/* * Copyright (c) 2009, 2019, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package jdk.nio.zipfs; import java.io.IOException; import java.io.OutputStream; import java.nio.file.attribute.PosixFilePermission; import java.time.DateTimeException; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.Arrays; import java.util.Date; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.regex.PatternSyntaxException; /** * @author Xueming Shen */ class ZipUtils { /** * The value indicating unix file attributes in CEN field "version made by". */ static final int FILE_ATTRIBUTES_UNIX = 3; /** * Constant used to calculate "version made by". */ static final int VERSION_BASE_UNIX = FILE_ATTRIBUTES_UNIX << 8; /** * The bit flag used to specify read permission by the owner. */ static final int POSIX_USER_READ = 0400; /** * The bit flag used to specify write permission by the owner. */ static final int POSIX_USER_WRITE = 0200; /** * The bit flag used to specify execute permission by the owner. */ static final int POSIX_USER_EXECUTE = 0100; /** * The bit flag used to specify read permission by the group. */ static final int POSIX_GROUP_READ = 040; /** * The bit flag used to specify write permission by the group. */ static final int POSIX_GROUP_WRITE = 020; /** * The bit flag used to specify execute permission by the group. */ static final int POSIX_GROUP_EXECUTE = 010; /** * The bit flag used to specify read permission by others. */ static final int POSIX_OTHER_READ = 04; /** * The bit flag used to specify write permission by others. */ static final int POSIX_OTHER_WRITE = 02; /** * The bit flag used to specify execute permission by others. */ static final int POSIX_OTHER_EXECUTE = 01; /** * Convert a {@link PosixFilePermission} object into the appropriate bit * flag. * * @param perm The {@link PosixFilePermission} object. * @return The bit flag as int. */ static int permToFlag(PosixFilePermission perm) { switch(perm) { case OWNER_READ: return POSIX_USER_READ; case OWNER_WRITE: return POSIX_USER_WRITE; case OWNER_EXECUTE: return POSIX_USER_EXECUTE; case GROUP_READ: return POSIX_GROUP_READ; case GROUP_WRITE: return POSIX_GROUP_WRITE; case GROUP_EXECUTE: return POSIX_GROUP_EXECUTE; case OTHERS_READ: return POSIX_OTHER_READ; case OTHERS_WRITE: return POSIX_OTHER_WRITE; case OTHERS_EXECUTE: return POSIX_OTHER_EXECUTE; default: return 0; } } /** * Converts a set of {@link PosixFilePermission}s into an int value where * the according bits are set. * * @param perms A Set of {@link PosixFilePermission} objects. * * @return A bit mask representing the input Set. */ static int permsToFlags(Set perms) { if (perms == null) { return -1; } int flags = 0; for (PosixFilePermission perm : perms) { flags |= permToFlag(perm); } return flags; } /* * Writes a 16-bit short to the output stream in little-endian byte order. */ public static void writeShort(OutputStream os, int v) throws IOException { os.write(v & 0xff); os.write((v >>> 8) & 0xff); } /* * Writes a 32-bit int to the output stream in little-endian byte order. */ public static void writeInt(OutputStream os, long v) throws IOException { os.write((int)(v & 0xff)); os.write((int)((v >>> 8) & 0xff)); os.write((int)((v >>> 16) & 0xff)); os.write((int)((v >>> 24) & 0xff)); } /* * Writes a 64-bit int to the output stream in little-endian byte order. */ public static void writeLong(OutputStream os, long v) throws IOException { os.write((int)(v & 0xff)); os.write((int)((v >>> 8) & 0xff)); os.write((int)((v >>> 16) & 0xff)); os.write((int)((v >>> 24) & 0xff)); os.write((int)((v >>> 32) & 0xff)); os.write((int)((v >>> 40) & 0xff)); os.write((int)((v >>> 48) & 0xff)); os.write((int)((v >>> 56) & 0xff)); } /* * Writes an array of bytes to the output stream. */ public static void writeBytes(OutputStream os, byte[] b) throws IOException { os.write(b, 0, b.length); } /* * Writes an array of bytes to the output stream. */ public static void writeBytes(OutputStream os, byte[] b, int off, int len) throws IOException { os.write(b, off, len); } /* * Append a slash at the end, if it does not have one yet */ public static byte[] toDirectoryPath(byte[] dir) { if (dir.length != 0 && dir[dir.length - 1] != '/') { dir = Arrays.copyOf(dir, dir.length + 1); dir[dir.length - 1] = '/'; } return dir; } /* * Converts DOS time to Java time (number of milliseconds since epoch). */ public static long dosToJavaTime(long dtime) { int year = (int) (((dtime >> 25) & 0x7f) + 1980); int month = (int) ((dtime >> 21) & 0x0f); int day = (int) ((dtime >> 16) & 0x1f); int hour = (int) ((dtime >> 11) & 0x1f); int minute = (int) ((dtime >> 5) & 0x3f); int second = (int) ((dtime << 1) & 0x3e); if (month > 0 && month < 13 && day > 0 && hour < 24 && minute < 60 && second < 60) { try { LocalDateTime ldt = LocalDateTime.of(year, month, day, hour, minute, second); return TimeUnit.MILLISECONDS.convert(ldt.toEpochSecond( ZoneId.systemDefault().getRules().getOffset(ldt)), TimeUnit.SECONDS); } catch (DateTimeException dte) { // ignore } } return overflowDosToJavaTime(year, month, day, hour, minute, second); } /* * Deal with corner cases where an arguably mal-formed DOS time is used */ @SuppressWarnings("deprecation") // Use of Date constructor private static long overflowDosToJavaTime(int year, int month, int day, int hour, int minute, int second) { return new Date(year - 1900, month - 1, day, hour, minute, second).getTime(); } /* * Converts Java time to DOS time. */ public static long javaToDosTime(long time) { Instant instant = Instant.ofEpochMilli(time); LocalDateTime ldt = LocalDateTime.ofInstant( instant, ZoneId.systemDefault()); int year = ldt.getYear() - 1980; if (year < 0) { return (1 << 21) | (1 << 16); } return (year << 25 | ldt.getMonthValue() << 21 | ldt.getDayOfMonth() << 16 | ldt.getHour() << 11 | ldt.getMinute() << 5 | ldt.getSecond() >> 1) & 0xffffffffL; } // used to adjust values between Windows and java epoch private static final long WINDOWS_EPOCH_IN_MICROSECONDS = -11644473600000000L; public static final long winToJavaTime(long wtime) { return TimeUnit.MILLISECONDS.convert( wtime / 10 + WINDOWS_EPOCH_IN_MICROSECONDS, TimeUnit.MICROSECONDS); } public static final long javaToWinTime(long time) { return (TimeUnit.MICROSECONDS.convert(time, TimeUnit.MILLISECONDS) - WINDOWS_EPOCH_IN_MICROSECONDS) * 10; } public static final long unixToJavaTime(long utime) { return TimeUnit.MILLISECONDS.convert(utime, TimeUnit.SECONDS); } public static final long javaToUnixTime(long time) { return TimeUnit.SECONDS.convert(time, TimeUnit.MILLISECONDS); } private static final String regexMetaChars = ".^$+{[]|()"; private static final String globMetaChars = "\\*?[{"; private static boolean isRegexMeta(char c) { return regexMetaChars.indexOf(c) != -1; } private static boolean isGlobMeta(char c) { return globMetaChars.indexOf(c) != -1; } private static char EOL = 0; //TBD private static char next(String glob, int i) { if (i < glob.length()) { return glob.charAt(i); } return EOL; } /* * Creates a regex pattern from the given glob expression. * * @throws PatternSyntaxException */ public static String toRegexPattern(String globPattern) { boolean inGroup = false; StringBuilder regex = new StringBuilder("^"); int i = 0; while (i < globPattern.length()) { char c = globPattern.charAt(i++); switch (c) { case '\\': // escape special characters if (i == globPattern.length()) { throw new PatternSyntaxException("No character to escape", globPattern, i - 1); } char next = globPattern.charAt(i++); if (isGlobMeta(next) || isRegexMeta(next)) { regex.append('\\'); } regex.append(next); break; case '/': regex.append(c); break; case '[': // don't match name separator in class regex.append("[[^/]&&["); if (next(globPattern, i) == '^') { // escape the regex negation char if it appears regex.append("\\^"); i++; } else { // negation if (next(globPattern, i) == '!') { regex.append('^'); i++; } // hyphen allowed at start if (next(globPattern, i) == '-') { regex.append('-'); i++; } } boolean hasRangeStart = false; char last = 0; while (i < globPattern.length()) { c = globPattern.charAt(i++); if (c == ']') { break; } if (c == '/') { throw new PatternSyntaxException("Explicit 'name separator' in class", globPattern, i - 1); } // TBD: how to specify ']' in a class? if (c == '\\' || c == '[' || c == '&' && next(globPattern, i) == '&') { // escape '\', '[' or "&&" for regex class regex.append('\\'); } regex.append(c); if (c == '-') { if (!hasRangeStart) { throw new PatternSyntaxException("Invalid range", globPattern, i - 1); } if ((c = next(globPattern, i++)) == EOL || c == ']') { break; } if (c < last) { throw new PatternSyntaxException("Invalid range", globPattern, i - 3); } regex.append(c); hasRangeStart = false; } else { hasRangeStart = true; last = c; } } if (c != ']') { throw new PatternSyntaxException("Missing ']", globPattern, i - 1); } regex.append("]]"); break; case '{': if (inGroup) { throw new PatternSyntaxException("Cannot nest groups", globPattern, i - 1); } regex.append("(?:(?:"); inGroup = true; break; case '}': if (inGroup) { regex.append("))"); inGroup = false; } else { regex.append('}'); } break; case ',': if (inGroup) { regex.append(")|(?:"); } else { regex.append(','); } break; case '*': if (next(globPattern, i) == '*') { // crosses directory boundaries regex.append(".*"); i++; } else { // within directory boundary regex.append("[^/]*"); } break; case '?': regex.append("[^/]"); break; default: if (isRegexMeta(c)) { regex.append('\\'); } regex.append(c); } } if (inGroup) { throw new PatternSyntaxException("Missing '}", globPattern, i - 1); } return regex.append('$').toString(); } }