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