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