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