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 // used to adjust values between Windows and java epoch 285 private static final long WINDOWS_EPOCH_IN_MICROSECONDS = -11644473600000000L; 286 public static final long winToJavaTime(long wtime) { 287 return TimeUnit.MILLISECONDS.convert( 288 wtime / 10 + WINDOWS_EPOCH_IN_MICROSECONDS, TimeUnit.MICROSECONDS); 289 } 290 291 public static final long javaToWinTime(long time) { 292 return (TimeUnit.MICROSECONDS.convert(time, TimeUnit.MILLISECONDS) 293 - WINDOWS_EPOCH_IN_MICROSECONDS) * 10; 294 } 295 296 public static final long unixToJavaTime(long utime) { 297 return TimeUnit.MILLISECONDS.convert(utime, TimeUnit.SECONDS); 298 } 299 300 public static final long javaToUnixTime(long time) { 301 return TimeUnit.SECONDS.convert(time, TimeUnit.MILLISECONDS); 302 } 303 304 private static final String regexMetaChars = ".^$+{[]|()"; 305 private static final String globMetaChars = "\\*?[{"; 306 private static boolean isRegexMeta(char c) { 307 return regexMetaChars.indexOf(c) != -1; 308 } 309 private static boolean isGlobMeta(char c) { 310 return globMetaChars.indexOf(c) != -1; 311 } 312 private static char EOL = 0; //TBD 313 private static char next(String glob, int i) { 314 if (i < glob.length()) { 315 return glob.charAt(i); 316 } 317 return EOL; 318 } 319 320 /* 321 * Creates a regex pattern from the given glob expression. 322 * 323 * @throws PatternSyntaxException 324 */ 325 public static String toRegexPattern(String globPattern) { 326 boolean inGroup = false; 327 StringBuilder regex = new StringBuilder("^"); 328 329 int i = 0; 330 while (i < globPattern.length()) { 331 char c = globPattern.charAt(i++); 332 switch (c) { 333 case '\\': 334 // escape special characters 335 if (i == globPattern.length()) { 336 throw new PatternSyntaxException("No character to escape", 337 globPattern, i - 1); 338 } 339 char next = globPattern.charAt(i++); 340 if (isGlobMeta(next) || isRegexMeta(next)) { 341 regex.append('\\'); 342 } 343 regex.append(next); 344 break; 345 case '/': 346 regex.append(c); 347 break; 348 case '[': 349 // don't match name separator in class 350 regex.append("[[^/]&&["); 351 if (next(globPattern, i) == '^') { 352 // escape the regex negation char if it appears 353 regex.append("\\^"); 354 i++; 355 } else { 356 // negation 357 if (next(globPattern, i) == '!') { 358 regex.append('^'); 359 i++; 360 } 361 // hyphen allowed at start 362 if (next(globPattern, i) == '-') { 363 regex.append('-'); 364 i++; 365 } 366 } 367 boolean hasRangeStart = false; 368 char last = 0; 369 while (i < globPattern.length()) { 370 c = globPattern.charAt(i++); 371 if (c == ']') { 372 break; 373 } 374 if (c == '/') { 375 throw new PatternSyntaxException("Explicit 'name separator' in class", 376 globPattern, i - 1); 377 } 378 // TBD: how to specify ']' in a class? 379 if (c == '\\' || c == '[' || 380 c == '&' && next(globPattern, i) == '&') { 381 // escape '\', '[' or "&&" for regex class 382 regex.append('\\'); 383 } 384 regex.append(c); 385 386 if (c == '-') { 387 if (!hasRangeStart) { 388 throw new PatternSyntaxException("Invalid range", 389 globPattern, i - 1); 390 } 391 if ((c = next(globPattern, i++)) == EOL || c == ']') { 392 break; 393 } 394 if (c < last) { 395 throw new PatternSyntaxException("Invalid range", 396 globPattern, i - 3); 397 } 398 regex.append(c); 399 hasRangeStart = false; 400 } else { 401 hasRangeStart = true; 402 last = c; 403 } 404 } 405 if (c != ']') { 406 throw new PatternSyntaxException("Missing ']", globPattern, i - 1); 407 } 408 regex.append("]]"); 409 break; 410 case '{': 411 if (inGroup) { 412 throw new PatternSyntaxException("Cannot nest groups", 413 globPattern, i - 1); 414 } 415 regex.append("(?:(?:"); 416 inGroup = true; 417 break; 418 case '}': 419 if (inGroup) { 420 regex.append("))"); 421 inGroup = false; 422 } else { 423 regex.append('}'); 424 } 425 break; 426 case ',': 427 if (inGroup) { 428 regex.append(")|(?:"); 429 } else { 430 regex.append(','); 431 } 432 break; 433 case '*': 434 if (next(globPattern, i) == '*') { 435 // crosses directory boundaries 436 regex.append(".*"); 437 i++; 438 } else { 439 // within directory boundary 440 regex.append("[^/]*"); 441 } 442 break; 443 case '?': 444 regex.append("[^/]"); 445 break; 446 default: 447 if (isRegexMeta(c)) { 448 regex.append('\\'); 449 } 450 regex.append(c); 451 } 452 } 453 if (inGroup) { 454 throw new PatternSyntaxException("Missing '}", globPattern, i - 1); 455 } 456 return regex.append('$').toString(); 457 } 458 }