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