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