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.time.DateTimeException; 31 import java.time.Instant; 32 import java.time.LocalDateTime; 33 import java.time.ZoneId; 34 import java.util.Arrays; 35 import java.util.Date; 36 import java.util.concurrent.TimeUnit; 37 import java.util.regex.PatternSyntaxException; 38 39 /** 40 * @author Xueming Shen 41 */ 42 class ZipUtils { 43 44 /* 45 * Writes a 16-bit short to the output stream in little-endian byte order. 46 */ writeShort(OutputStream os, int v)47 public static void writeShort(OutputStream os, int v) throws IOException { 48 os.write(v & 0xff); 49 os.write((v >>> 8) & 0xff); 50 } 51 52 /* 53 * Writes a 32-bit int to the output stream in little-endian byte order. 54 */ writeInt(OutputStream os, long v)55 public static void writeInt(OutputStream os, long v) throws IOException { 56 os.write((int)(v & 0xff)); 57 os.write((int)((v >>> 8) & 0xff)); 58 os.write((int)((v >>> 16) & 0xff)); 59 os.write((int)((v >>> 24) & 0xff)); 60 } 61 62 /* 63 * Writes a 64-bit int to the output stream in little-endian byte order. 64 */ writeLong(OutputStream os, long v)65 public static void writeLong(OutputStream os, long v) throws IOException { 66 os.write((int)(v & 0xff)); 67 os.write((int)((v >>> 8) & 0xff)); 68 os.write((int)((v >>> 16) & 0xff)); 69 os.write((int)((v >>> 24) & 0xff)); 70 os.write((int)((v >>> 32) & 0xff)); 71 os.write((int)((v >>> 40) & 0xff)); 72 os.write((int)((v >>> 48) & 0xff)); 73 os.write((int)((v >>> 56) & 0xff)); 74 } 75 76 /* 77 * Writes an array of bytes to the output stream. 78 */ writeBytes(OutputStream os, byte[] b)79 public static void writeBytes(OutputStream os, byte[] b) 80 throws IOException 81 { 82 os.write(b, 0, b.length); 83 } 84 85 /* 86 * Writes an array of bytes to the output stream. 87 */ writeBytes(OutputStream os, byte[] b, int off, int len)88 public static void writeBytes(OutputStream os, byte[] b, int off, int len) 89 throws IOException 90 { 91 os.write(b, off, len); 92 } 93 94 /* 95 * Append a slash at the end, if it does not have one yet 96 */ toDirectoryPath(byte[] dir)97 public static byte[] toDirectoryPath(byte[] dir) { 98 if (dir.length != 0 && dir[dir.length - 1] != '/') { 99 dir = Arrays.copyOf(dir, dir.length + 1); 100 dir[dir.length - 1] = '/'; 101 } 102 return dir; 103 } 104 105 /* 106 * Converts DOS time to Java time (number of milliseconds since epoch). 107 */ dosToJavaTime(long dtime)108 public static long dosToJavaTime(long dtime) { 109 int year = (int) (((dtime >> 25) & 0x7f) + 1980); 110 int month = (int) ((dtime >> 21) & 0x0f); 111 int day = (int) ((dtime >> 16) & 0x1f); 112 int hour = (int) ((dtime >> 11) & 0x1f); 113 int minute = (int) ((dtime >> 5) & 0x3f); 114 int second = (int) ((dtime << 1) & 0x3e); 115 116 if (month > 0 && month < 13 && day > 0 && hour < 24 && minute < 60 && second < 60) { 117 try { 118 LocalDateTime ldt = LocalDateTime.of(year, month, day, hour, minute, second); 119 return TimeUnit.MILLISECONDS.convert(ldt.toEpochSecond( 120 ZoneId.systemDefault().getRules().getOffset(ldt)), TimeUnit.SECONDS); 121 } catch (DateTimeException dte) { 122 // ignore 123 } 124 } 125 return overflowDosToJavaTime(year, month, day, hour, minute, second); 126 } 127 128 /* 129 * Deal with corner cases where an arguably mal-formed DOS time is used 130 */ 131 @SuppressWarnings("deprecation") // Use of Date constructor overflowDosToJavaTime(int year, int month, int day, int hour, int minute, int second)132 private static long overflowDosToJavaTime(int year, int month, int day, 133 int hour, int minute, int second) { 134 return new Date(year - 1900, month - 1, day, hour, minute, second).getTime(); 135 } 136 137 /* 138 * Converts Java time to DOS time. 139 */ javaToDosTime(long time)140 public static long javaToDosTime(long time) { 141 Instant instant = Instant.ofEpochMilli(time); 142 LocalDateTime ldt = LocalDateTime.ofInstant( 143 instant, ZoneId.systemDefault()); 144 int year = ldt.getYear() - 1980; 145 if (year < 0) { 146 return (1 << 21) | (1 << 16); 147 } 148 return (year << 25 | 149 ldt.getMonthValue() << 21 | 150 ldt.getDayOfMonth() << 16 | 151 ldt.getHour() << 11 | 152 ldt.getMinute() << 5 | 153 ldt.getSecond() >> 1) & 0xffffffffL; 154 } 155 156 // used to adjust values between Windows and java epoch 157 private static final long WINDOWS_EPOCH_IN_MICROSECONDS = -11644473600000000L; winToJavaTime(long wtime)158 public static final long winToJavaTime(long wtime) { 159 return TimeUnit.MILLISECONDS.convert( 160 wtime / 10 + WINDOWS_EPOCH_IN_MICROSECONDS, TimeUnit.MICROSECONDS); 161 } 162 javaToWinTime(long time)163 public static final long javaToWinTime(long time) { 164 return (TimeUnit.MICROSECONDS.convert(time, TimeUnit.MILLISECONDS) 165 - WINDOWS_EPOCH_IN_MICROSECONDS) * 10; 166 } 167 unixToJavaTime(long utime)168 public static final long unixToJavaTime(long utime) { 169 return TimeUnit.MILLISECONDS.convert(utime, TimeUnit.SECONDS); 170 } 171 javaToUnixTime(long time)172 public static final long javaToUnixTime(long time) { 173 return TimeUnit.SECONDS.convert(time, TimeUnit.MILLISECONDS); 174 } 175 176 private static final String regexMetaChars = ".^$+{[]|()"; 177 private static final String globMetaChars = "\\*?[{"; isRegexMeta(char c)178 private static boolean isRegexMeta(char c) { 179 return regexMetaChars.indexOf(c) != -1; 180 } isGlobMeta(char c)181 private static boolean isGlobMeta(char c) { 182 return globMetaChars.indexOf(c) != -1; 183 } 184 private static char EOL = 0; //TBD next(String glob, int i)185 private static char next(String glob, int i) { 186 if (i < glob.length()) { 187 return glob.charAt(i); 188 } 189 return EOL; 190 } 191 192 /* 193 * Creates a regex pattern from the given glob expression. 194 * 195 * @throws PatternSyntaxException 196 */ toRegexPattern(String globPattern)197 public static String toRegexPattern(String globPattern) { 198 boolean inGroup = false; 199 StringBuilder regex = new StringBuilder("^"); 200 201 int i = 0; 202 while (i < globPattern.length()) { 203 char c = globPattern.charAt(i++); 204 switch (c) { 205 case '\\': 206 // escape special characters 207 if (i == globPattern.length()) { 208 throw new PatternSyntaxException("No character to escape", 209 globPattern, i - 1); 210 } 211 char next = globPattern.charAt(i++); 212 if (isGlobMeta(next) || isRegexMeta(next)) { 213 regex.append('\\'); 214 } 215 regex.append(next); 216 break; 217 case '/': 218 regex.append(c); 219 break; 220 case '[': 221 // don't match name separator in class 222 regex.append("[[^/]&&["); 223 if (next(globPattern, i) == '^') { 224 // escape the regex negation char if it appears 225 regex.append("\\^"); 226 i++; 227 } else { 228 // negation 229 if (next(globPattern, i) == '!') { 230 regex.append('^'); 231 i++; 232 } 233 // hyphen allowed at start 234 if (next(globPattern, i) == '-') { 235 regex.append('-'); 236 i++; 237 } 238 } 239 boolean hasRangeStart = false; 240 char last = 0; 241 while (i < globPattern.length()) { 242 c = globPattern.charAt(i++); 243 if (c == ']') { 244 break; 245 } 246 if (c == '/') { 247 throw new PatternSyntaxException("Explicit 'name separator' in class", 248 globPattern, i - 1); 249 } 250 // TBD: how to specify ']' in a class? 251 if (c == '\\' || c == '[' || 252 c == '&' && next(globPattern, i) == '&') { 253 // escape '\', '[' or "&&" for regex class 254 regex.append('\\'); 255 } 256 regex.append(c); 257 258 if (c == '-') { 259 if (!hasRangeStart) { 260 throw new PatternSyntaxException("Invalid range", 261 globPattern, i - 1); 262 } 263 if ((c = next(globPattern, i++)) == EOL || c == ']') { 264 break; 265 } 266 if (c < last) { 267 throw new PatternSyntaxException("Invalid range", 268 globPattern, i - 3); 269 } 270 regex.append(c); 271 hasRangeStart = false; 272 } else { 273 hasRangeStart = true; 274 last = c; 275 } 276 } 277 if (c != ']') { 278 throw new PatternSyntaxException("Missing ']", globPattern, i - 1); 279 } 280 regex.append("]]"); 281 break; 282 case '{': 283 if (inGroup) { 284 throw new PatternSyntaxException("Cannot nest groups", 285 globPattern, i - 1); 286 } 287 regex.append("(?:(?:"); 288 inGroup = true; 289 break; 290 case '}': 291 if (inGroup) { 292 regex.append("))"); 293 inGroup = false; 294 } else { 295 regex.append('}'); 296 } 297 break; 298 case ',': 299 if (inGroup) { 300 regex.append(")|(?:"); 301 } else { 302 regex.append(','); 303 } 304 break; 305 case '*': 306 if (next(globPattern, i) == '*') { 307 // crosses directory boundaries 308 regex.append(".*"); 309 i++; 310 } else { 311 // within directory boundary 312 regex.append("[^/]*"); 313 } 314 break; 315 case '?': 316 regex.append("[^/]"); 317 break; 318 default: 319 if (isRegexMeta(c)) { 320 regex.append('\\'); 321 } 322 regex.append(c); 323 } 324 } 325 if (inGroup) { 326 throw new PatternSyntaxException("Missing '}", globPattern, i - 1); 327 } 328 return regex.append('$').toString(); 329 } 330 } 331