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 permCache.put(flags, perms); 182 } 183 return new HashSet<>(perms); 184 } 185 186 /* 187 * Writes a 16-bit short to the output stream in little-endian byte order. 188 */ 189 public static void writeShort(OutputStream os, int v) throws IOException { 190 os.write(v & 0xff); 191 os.write((v >>> 8) & 0xff); 192 } 193 194 /* 195 * Writes a 32-bit int to the output stream in little-endian byte order. 196 */ 197 public static void writeInt(OutputStream os, long v) throws IOException { 198 os.write((int)(v & 0xff)); 199 os.write((int)((v >>> 8) & 0xff)); 200 os.write((int)((v >>> 16) & 0xff)); 201 os.write((int)((v >>> 24) & 0xff)); 202 } 203 204 /* 205 * Writes a 64-bit int to the output stream in little-endian byte order. 206 */ 207 public static void writeLong(OutputStream os, long v) throws IOException { 208 os.write((int)(v & 0xff)); 209 os.write((int)((v >>> 8) & 0xff)); 210 os.write((int)((v >>> 16) & 0xff)); 211 os.write((int)((v >>> 24) & 0xff)); 212 os.write((int)((v >>> 32) & 0xff)); 213 os.write((int)((v >>> 40) & 0xff)); 214 os.write((int)((v >>> 48) & 0xff)); 215 os.write((int)((v >>> 56) & 0xff)); 216 } 217 218 /* 219 * Writes an array of bytes to the output stream. 220 */ 221 public static void writeBytes(OutputStream os, byte[] b) 222 throws IOException 223 { 224 os.write(b, 0, b.length); 225 } 226 227 /* 228 * Writes an array of bytes to the output stream. 229 */ 230 public static void writeBytes(OutputStream os, byte[] b, int off, int len) 231 throws IOException 232 { 233 os.write(b, off, len); 234 } 235 236 /* 237 * Append a slash at the end, if it does not have one yet 238 */ 239 public static byte[] toDirectoryPath(byte[] dir) { 240 if (dir.length != 0 && dir[dir.length - 1] != '/') { 241 dir = Arrays.copyOf(dir, dir.length + 1); 242 dir[dir.length - 1] = '/'; 243 } 244 return dir; 245 } 246 247 /* 248 * Converts DOS time to Java time (number of milliseconds since epoch). 249 */ 250 public static long dosToJavaTime(long dtime) { 251 int year = (int) (((dtime >> 25) & 0x7f) + 1980); 252 int month = (int) ((dtime >> 21) & 0x0f); 253 int day = (int) ((dtime >> 16) & 0x1f); 254 int hour = (int) ((dtime >> 11) & 0x1f); 255 int minute = (int) ((dtime >> 5) & 0x3f); 256 int second = (int) ((dtime << 1) & 0x3e); 257 258 if (month > 0 && month < 13 && day > 0 && hour < 24 && minute < 60 && second < 60) { 259 try { 260 LocalDateTime ldt = LocalDateTime.of(year, month, day, hour, minute, second); 261 return TimeUnit.MILLISECONDS.convert(ldt.toEpochSecond( 262 ZoneId.systemDefault().getRules().getOffset(ldt)), TimeUnit.SECONDS); 263 } catch (DateTimeException dte) { 264 // ignore 265 } 266 } 267 return overflowDosToJavaTime(year, month, day, hour, minute, second); 268 } 269 270 /* 271 * Deal with corner cases where an arguably mal-formed DOS time is used 272 */ 273 @SuppressWarnings("deprecation") // Use of Date constructor 274 private static long overflowDosToJavaTime(int year, int month, int day, 275 int hour, int minute, int second) { 276 return new Date(year - 1900, month - 1, day, hour, minute, second).getTime(); 277 } 278 279 /* 280 * Converts Java time to DOS time. 281 */ 282 public static long javaToDosTime(long time) { 283 Instant instant = Instant.ofEpochMilli(time); 284 LocalDateTime ldt = LocalDateTime.ofInstant( 285 instant, ZoneId.systemDefault()); 286 int year = ldt.getYear() - 1980; 287 if (year < 0) { 288 return (1 << 21) | (1 << 16); 289 } 290 return (year << 25 | 291 ldt.getMonthValue() << 21 | 292 ldt.getDayOfMonth() << 16 | 293 ldt.getHour() << 11 | 294 ldt.getMinute() << 5 | 295 ldt.getSecond() >> 1) & 0xffffffffL; 296 } 297 298 299 // used to adjust values between Windows and java epoch 300 private static final long WINDOWS_EPOCH_IN_MICROSECONDS = -11644473600000000L; 301 public static final long winToJavaTime(long wtime) { 302 return TimeUnit.MILLISECONDS.convert( 303 wtime / 10 + WINDOWS_EPOCH_IN_MICROSECONDS, TimeUnit.MICROSECONDS); 304 } 305 306 public static final long javaToWinTime(long time) { 307 return (TimeUnit.MICROSECONDS.convert(time, TimeUnit.MILLISECONDS) 308 - WINDOWS_EPOCH_IN_MICROSECONDS) * 10; 309 } 310 311 public static final long unixToJavaTime(long utime) { 312 return TimeUnit.MILLISECONDS.convert(utime, TimeUnit.SECONDS); 313 } 314 315 public static final long javaToUnixTime(long time) { 316 return TimeUnit.SECONDS.convert(time, TimeUnit.MILLISECONDS); 317 } 318 319 private static final String regexMetaChars = ".^$+{[]|()"; 320 private static final String globMetaChars = "\\*?[{"; 321 private static boolean isRegexMeta(char c) { 322 return regexMetaChars.indexOf(c) != -1; 323 } 324 private static boolean isGlobMeta(char c) { 325 return globMetaChars.indexOf(c) != -1; 326 } 327 private static char EOL = 0; //TBD 328 private static char next(String glob, int i) { 329 if (i < glob.length()) { 330 return glob.charAt(i); 331 } 332 return EOL; 333 } 334 335 /* 336 * Creates a regex pattern from the given glob expression. 337 * 338 * @throws PatternSyntaxException 339 */ 340 public static String toRegexPattern(String globPattern) { 341 boolean inGroup = false; 342 StringBuilder regex = new StringBuilder("^"); 343 344 int i = 0; 345 while (i < globPattern.length()) { 346 char c = globPattern.charAt(i++); 347 switch (c) { 348 case '\\': 349 // escape special characters 350 if (i == globPattern.length()) { 351 throw new PatternSyntaxException("No character to escape", 352 globPattern, i - 1); 353 } 354 char next = globPattern.charAt(i++); 355 if (isGlobMeta(next) || isRegexMeta(next)) { 356 regex.append('\\'); 357 } 358 regex.append(next); 359 break; 360 case '/': 361 regex.append(c); 362 break; 363 case '[': 364 // don't match name separator in class 365 regex.append("[[^/]&&["); 366 if (next(globPattern, i) == '^') { 367 // escape the regex negation char if it appears 368 regex.append("\\^"); 369 i++; 370 } else { 371 // negation 372 if (next(globPattern, i) == '!') { 373 regex.append('^'); 374 i++; 375 } 376 // hyphen allowed at start 377 if (next(globPattern, i) == '-') { 378 regex.append('-'); 379 i++; 380 } 381 } 382 boolean hasRangeStart = false; 383 char last = 0; 384 while (i < globPattern.length()) { 385 c = globPattern.charAt(i++); 386 if (c == ']') { 387 break; 388 } 389 if (c == '/') { 390 throw new PatternSyntaxException("Explicit 'name separator' in class", 391 globPattern, i - 1); 392 } 393 // TBD: how to specify ']' in a class? 394 if (c == '\\' || c == '[' || 395 c == '&' && next(globPattern, i) == '&') { 396 // escape '\', '[' or "&&" for regex class 397 regex.append('\\'); 398 } 399 regex.append(c); 400 401 if (c == '-') { 402 if (!hasRangeStart) { 403 throw new PatternSyntaxException("Invalid range", 404 globPattern, i - 1); 405 } 406 if ((c = next(globPattern, i++)) == EOL || c == ']') { 407 break; 408 } 409 if (c < last) { 410 throw new PatternSyntaxException("Invalid range", 411 globPattern, i - 3); 412 } 413 regex.append(c); 414 hasRangeStart = false; 415 } else { 416 hasRangeStart = true; 417 last = c; 418 } 419 } 420 if (c != ']') { 421 throw new PatternSyntaxException("Missing ']", globPattern, i - 1); 422 } 423 regex.append("]]"); 424 break; 425 case '{': 426 if (inGroup) { 427 throw new PatternSyntaxException("Cannot nest groups", 428 globPattern, i - 1); 429 } 430 regex.append("(?:(?:"); 431 inGroup = true; 432 break; 433 case '}': 434 if (inGroup) { 435 regex.append("))"); 436 inGroup = false; 437 } else { 438 regex.append('}'); 439 } 440 break; 441 case ',': 442 if (inGroup) { 443 regex.append(")|(?:"); 444 } else { 445 regex.append(','); 446 } 447 break; 448 case '*': 449 if (next(globPattern, i) == '*') { 450 // crosses directory boundaries 451 regex.append(".*"); 452 i++; 453 } else { 454 // within directory boundary 455 regex.append("[^/]*"); 456 } 457 break; 458 case '?': 459 regex.append("[^/]"); 460 break; 461 default: 462 if (isRegexMeta(c)) { 463 regex.append('\\'); 464 } 465 regex.append(c); 466 } 467 } 468 if (inGroup) { 469 throw new PatternSyntaxException("Missing '}", globPattern, i - 1); 470 } 471 return regex.append('$').toString(); 472 } 473 }