1 /*
   2  * Copyright (c) 2009, 2019, 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.nio.file.attribute.PosixFilePermission;
  31 import java.time.DateTimeException;
  32 import java.time.Instant;
  33 import java.time.LocalDateTime;
  34 import java.time.ZoneId;
  35 import java.util.Arrays;
  36 import java.util.Date;
  37 import java.util.Set;
  38 import java.util.concurrent.TimeUnit;
  39 import java.util.regex.PatternSyntaxException;
  40 
  41 /**
  42  * @author Xueming Shen
  43  */
  44 class ZipUtils {
  45 
  46     /**
  47      * The bit flag used to specify read permission by the owner.
  48      */
  49     static final int POSIX_USER_READ = 0400;
  50 
  51     /**
  52      * The bit flag used to specify write permission by the owner.
  53      */
  54     static final int POSIX_USER_WRITE = 0200;
  55 
  56     /**
  57      * The bit flag used to specify execute permission by the owner.
  58      */
  59     static final int POSIX_USER_EXECUTE = 0100;
  60 
  61     /**
  62      * The bit flag used to specify read permission by the group.
  63      */
  64     static final int POSIX_GROUP_READ = 040;
  65 
  66     /**
  67      * The bit flag used to specify write permission by the group.
  68      */
  69     static final int POSIX_GROUP_WRITE = 020;
  70 
  71     /**
  72      * The bit flag used to specify execute permission by the group.
  73      */
  74     static final int POSIX_GROUP_EXECUTE = 010;
  75 
  76     /**
  77      * The bit flag used to specify read permission by others.
  78      */
  79     static final int POSIX_OTHER_READ = 04;
  80 
  81     /**
  82      * The bit flag used to specify write permission by others.
  83      */
  84     static final int POSIX_OTHER_WRITE = 02;
  85 
  86     /**
  87      * The bit flag used to specify execute permission by others.
  88      */
  89     static final int POSIX_OTHER_EXECUTE = 01;
  90 
  91     /**
  92      * Convert a {@link PosixFilePermission} object into the appropriate bit
  93      * flag.
  94      *
  95      * @param perm The {@link PosixFilePermission} object.
  96      * @return The bit flag as int.
  97      */
  98     static int permToFlag(PosixFilePermission perm) {
  99         switch(perm) {
 100         case OWNER_READ:
 101             return POSIX_USER_READ;
 102         case OWNER_WRITE:
 103             return POSIX_USER_WRITE;
 104         case OWNER_EXECUTE:
 105             return POSIX_USER_EXECUTE;
 106         case GROUP_READ:
 107             return POSIX_GROUP_READ;
 108         case GROUP_WRITE:
 109             return POSIX_GROUP_WRITE;
 110         case GROUP_EXECUTE:
 111             return POSIX_GROUP_EXECUTE;
 112         case OTHERS_READ:
 113             return POSIX_OTHER_READ;
 114         case OTHERS_WRITE:
 115             return POSIX_OTHER_WRITE;
 116         case OTHERS_EXECUTE:
 117             return POSIX_OTHER_EXECUTE;
 118         default:
 119             return 0;
 120         }
 121     }
 122 
 123     /**
 124      * Converts a set of {@link PosixFilePermission}s into an int value where
 125      * the according bits are set.
 126      *
 127      * @param perms A Set of {@link PosixFilePermission} objects.
 128      *
 129      * @return A bit mask representing the input Set.
 130      */
 131     static int permsToFlags(Set<PosixFilePermission> perms) {
 132         if (perms == null) {
 133             return -1;
 134         }
 135         int flags = 0;
 136         for (PosixFilePermission perm : perms) {
 137             flags |= permToFlag(perm);
 138         }
 139         return flags;
 140     }
 141 
 142     /*
 143      * Writes a 16-bit short to the output stream in little-endian byte order.
 144      */
 145     public static void writeShort(OutputStream os, int v) throws IOException {
 146         os.write(v & 0xff);
 147         os.write((v >>> 8) & 0xff);
 148     }
 149 
 150     /*
 151      * Writes a 32-bit int to the output stream in little-endian byte order.
 152      */
 153     public static void writeInt(OutputStream os, long v) throws IOException {
 154         os.write((int)(v & 0xff));
 155         os.write((int)((v >>>  8) & 0xff));
 156         os.write((int)((v >>> 16) & 0xff));
 157         os.write((int)((v >>> 24) & 0xff));
 158     }
 159 
 160     /*
 161      * Writes a 64-bit int to the output stream in little-endian byte order.
 162      */
 163     public static void writeLong(OutputStream os, long v) throws IOException {
 164         os.write((int)(v & 0xff));
 165         os.write((int)((v >>>  8) & 0xff));
 166         os.write((int)((v >>> 16) & 0xff));
 167         os.write((int)((v >>> 24) & 0xff));
 168         os.write((int)((v >>> 32) & 0xff));
 169         os.write((int)((v >>> 40) & 0xff));
 170         os.write((int)((v >>> 48) & 0xff));
 171         os.write((int)((v >>> 56) & 0xff));
 172     }
 173 
 174     /*
 175      * Writes an array of bytes to the output stream.
 176      */
 177     public static void writeBytes(OutputStream os, byte[] b)
 178         throws IOException
 179     {
 180         os.write(b, 0, b.length);
 181     }
 182 
 183     /*
 184      * Writes an array of bytes to the output stream.
 185      */
 186     public static void writeBytes(OutputStream os, byte[] b, int off, int len)
 187         throws IOException
 188     {
 189         os.write(b, off, len);
 190     }
 191 
 192     /*
 193      * Append a slash at the end, if it does not have one yet
 194      */
 195     public static byte[] toDirectoryPath(byte[] dir) {
 196         if (dir.length != 0 && dir[dir.length - 1] != '/') {
 197             dir = Arrays.copyOf(dir, dir.length + 1);
 198             dir[dir.length - 1] = '/';
 199         }
 200         return dir;
 201     }
 202 
 203     /*
 204      * Converts DOS time to Java time (number of milliseconds since epoch).
 205      */
 206     public static long dosToJavaTime(long dtime) {
 207         int year = (int) (((dtime >> 25) & 0x7f) + 1980);
 208         int month = (int) ((dtime >> 21) & 0x0f);
 209         int day = (int) ((dtime >> 16) & 0x1f);
 210         int hour = (int) ((dtime >> 11) & 0x1f);
 211         int minute = (int) ((dtime >> 5) & 0x3f);
 212         int second = (int) ((dtime << 1) & 0x3e);
 213 
 214         if (month > 0 && month < 13 && day > 0 && hour < 24 && minute < 60 && second < 60) {
 215             try {
 216                 LocalDateTime ldt = LocalDateTime.of(year, month, day, hour, minute, second);
 217                 return TimeUnit.MILLISECONDS.convert(ldt.toEpochSecond(
 218                         ZoneId.systemDefault().getRules().getOffset(ldt)), TimeUnit.SECONDS);
 219             } catch (DateTimeException dte) {
 220                 // ignore
 221             }
 222         }
 223         return overflowDosToJavaTime(year, month, day, hour, minute, second);
 224     }
 225 
 226     /*
 227      * Deal with corner cases where an arguably mal-formed DOS time is used
 228      */
 229     @SuppressWarnings("deprecation") // Use of Date constructor
 230     private static long overflowDosToJavaTime(int year, int month, int day,
 231                                               int hour, int minute, int second) {
 232         return new Date(year - 1900, month - 1, day, hour, minute, second).getTime();
 233     }
 234 
 235     /*
 236      * Converts Java time to DOS time.
 237      */
 238     public static long javaToDosTime(long time) {
 239         Instant instant = Instant.ofEpochMilli(time);
 240         LocalDateTime ldt = LocalDateTime.ofInstant(
 241                 instant, ZoneId.systemDefault());
 242         int year = ldt.getYear() - 1980;
 243         if (year < 0) {
 244             return (1 << 21) | (1 << 16);
 245         }
 246         return (year << 25 |
 247             ldt.getMonthValue() << 21 |
 248             ldt.getDayOfMonth() << 16 |
 249             ldt.getHour() << 11 |
 250             ldt.getMinute() << 5 |
 251             ldt.getSecond() >> 1) & 0xffffffffL;
 252     }
 253 
 254     // used to adjust values between Windows and java epoch
 255     private static final long WINDOWS_EPOCH_IN_MICROSECONDS = -11644473600000000L;
 256     public static final long winToJavaTime(long wtime) {
 257         return TimeUnit.MILLISECONDS.convert(
 258                wtime / 10 + WINDOWS_EPOCH_IN_MICROSECONDS, TimeUnit.MICROSECONDS);
 259     }
 260 
 261     public static final long javaToWinTime(long time) {
 262         return (TimeUnit.MICROSECONDS.convert(time, TimeUnit.MILLISECONDS)
 263                - WINDOWS_EPOCH_IN_MICROSECONDS) * 10;
 264     }
 265 
 266     public static final long unixToJavaTime(long utime) {
 267         return TimeUnit.MILLISECONDS.convert(utime, TimeUnit.SECONDS);
 268     }
 269 
 270     public static final long javaToUnixTime(long time) {
 271         return TimeUnit.SECONDS.convert(time, TimeUnit.MILLISECONDS);
 272     }
 273 
 274     private static final String regexMetaChars = ".^$+{[]|()";
 275     private static final String globMetaChars = "\\*?[{";
 276     private static boolean isRegexMeta(char c) {
 277         return regexMetaChars.indexOf(c) != -1;
 278     }
 279     private static boolean isGlobMeta(char c) {
 280         return globMetaChars.indexOf(c) != -1;
 281     }
 282     private static char EOL = 0;  //TBD
 283     private static char next(String glob, int i) {
 284         if (i < glob.length()) {
 285             return glob.charAt(i);
 286         }
 287         return EOL;
 288     }
 289 
 290     /*
 291      * Creates a regex pattern from the given glob expression.
 292      *
 293      * @throws  PatternSyntaxException
 294      */
 295     public static String toRegexPattern(String globPattern) {
 296         boolean inGroup = false;
 297         StringBuilder regex = new StringBuilder("^");
 298 
 299         int i = 0;
 300         while (i < globPattern.length()) {
 301             char c = globPattern.charAt(i++);
 302             switch (c) {
 303                 case '\\':
 304                     // escape special characters
 305                     if (i == globPattern.length()) {
 306                         throw new PatternSyntaxException("No character to escape",
 307                                 globPattern, i - 1);
 308                     }
 309                     char next = globPattern.charAt(i++);
 310                     if (isGlobMeta(next) || isRegexMeta(next)) {
 311                         regex.append('\\');
 312                     }
 313                     regex.append(next);
 314                     break;
 315                 case '/':
 316                     regex.append(c);
 317                     break;
 318                 case '[':
 319                     // don't match name separator in class
 320                     regex.append("[[^/]&&[");
 321                     if (next(globPattern, i) == '^') {
 322                         // escape the regex negation char if it appears
 323                         regex.append("\\^");
 324                         i++;
 325                     } else {
 326                         // negation
 327                         if (next(globPattern, i) == '!') {
 328                             regex.append('^');
 329                             i++;
 330                         }
 331                         // hyphen allowed at start
 332                         if (next(globPattern, i) == '-') {
 333                             regex.append('-');
 334                             i++;
 335                         }
 336                     }
 337                     boolean hasRangeStart = false;
 338                     char last = 0;
 339                     while (i < globPattern.length()) {
 340                         c = globPattern.charAt(i++);
 341                         if (c == ']') {
 342                             break;
 343                         }
 344                         if (c == '/') {
 345                             throw new PatternSyntaxException("Explicit 'name separator' in class",
 346                                     globPattern, i - 1);
 347                         }
 348                         // TBD: how to specify ']' in a class?
 349                         if (c == '\\' || c == '[' ||
 350                                 c == '&' && next(globPattern, i) == '&') {
 351                             // escape '\', '[' or "&&" for regex class
 352                             regex.append('\\');
 353                         }
 354                         regex.append(c);
 355 
 356                         if (c == '-') {
 357                             if (!hasRangeStart) {
 358                                 throw new PatternSyntaxException("Invalid range",
 359                                         globPattern, i - 1);
 360                             }
 361                             if ((c = next(globPattern, i++)) == EOL || c == ']') {
 362                                 break;
 363                             }
 364                             if (c < last) {
 365                                 throw new PatternSyntaxException("Invalid range",
 366                                         globPattern, i - 3);
 367                             }
 368                             regex.append(c);
 369                             hasRangeStart = false;
 370                         } else {
 371                             hasRangeStart = true;
 372                             last = c;
 373                         }
 374                     }
 375                     if (c != ']') {
 376                         throw new PatternSyntaxException("Missing ']", globPattern, i - 1);
 377                     }
 378                     regex.append("]]");
 379                     break;
 380                 case '{':
 381                     if (inGroup) {
 382                         throw new PatternSyntaxException("Cannot nest groups",
 383                                 globPattern, i - 1);
 384                     }
 385                     regex.append("(?:(?:");
 386                     inGroup = true;
 387                     break;
 388                 case '}':
 389                     if (inGroup) {
 390                         regex.append("))");
 391                         inGroup = false;
 392                     } else {
 393                         regex.append('}');
 394                     }
 395                     break;
 396                 case ',':
 397                     if (inGroup) {
 398                         regex.append(")|(?:");
 399                     } else {
 400                         regex.append(',');
 401                     }
 402                     break;
 403                 case '*':
 404                     if (next(globPattern, i) == '*') {
 405                         // crosses directory boundaries
 406                         regex.append(".*");
 407                         i++;
 408                     } else {
 409                         // within directory boundary
 410                         regex.append("[^/]*");
 411                     }
 412                     break;
 413                 case '?':
 414                    regex.append("[^/]");
 415                    break;
 416                 default:
 417                     if (isRegexMeta(c)) {
 418                         regex.append('\\');
 419                     }
 420                     regex.append(c);
 421             }
 422         }
 423         if (inGroup) {
 424             throw new PatternSyntaxException("Missing '}", globPattern, i - 1);
 425         }
 426         return regex.append('$').toString();
 427     }
 428 }