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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 */ 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; 158 public static final long winToJavaTime(long wtime) { 159 return TimeUnit.MILLISECONDS.convert( 160 wtime / 10 + WINDOWS_EPOCH_IN_MICROSECONDS, TimeUnit.MICROSECONDS); 161 } 162 163 public static final long javaToWinTime(long time) { 164 return (TimeUnit.MICROSECONDS.convert(time, TimeUnit.MILLISECONDS) 165 - WINDOWS_EPOCH_IN_MICROSECONDS) * 10; 166 } 167 168 public static final long unixToJavaTime(long utime) { 169 return TimeUnit.MILLISECONDS.convert(utime, TimeUnit.SECONDS); 170 } 171 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 = "\\*?[{"; 178 private static boolean isRegexMeta(char c) { 179 return regexMetaChars.indexOf(c) != -1; 180 } 181 private static boolean isGlobMeta(char c) { 182 return globMetaChars.indexOf(c) != -1; 183 } 184 private static char EOL = 0; //TBD 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 */ 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 }