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