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     @SuppressWarnings("deprecation")
 107     public static long dosToJavaTime(long dtime) {
 108         Date d = new Date((int)(((dtime >> 25) & 0x7f) + 80),
 109                           (int)(((dtime >> 21) & 0x0f) - 1),
 110                           (int)((dtime >> 16) & 0x1f),
 111                           (int)((dtime >> 11) & 0x1f),
 112                           (int)((dtime >> 5) & 0x3f),
 113                           (int)((dtime << 1) & 0x3e));
 114         return d.getTime();
 115     }
 116 
 117     /*
 118      * Converts Java time to DOS time.
 119      */
 120     @SuppressWarnings("deprecation")
 121     public static long javaToDosTime(long time) {
 122         Date d = new Date(time);
 123         int year = d.getYear() + 1900;
 124         if (year < 1980) {
 125             return (1 << 21) | (1 << 16);
 126         }
 127         return (year - 1980) << 25 | (d.getMonth() + 1) << 21 |
 128                d.getDate() << 16 | d.getHours() << 11 | d.getMinutes() << 5 |
 129                d.getSeconds() >> 1;
 130     }
 131 
 132 
 133     // used to adjust values between Windows and java epoch
 134     private static final long WINDOWS_EPOCH_IN_MICROSECONDS = -11644473600000000L;
 135     public static final long winToJavaTime(long wtime) {
 136         return TimeUnit.MILLISECONDS.convert(
 137                wtime / 10 + WINDOWS_EPOCH_IN_MICROSECONDS, TimeUnit.MICROSECONDS);
 138     }
 139 
 140     public static final long javaToWinTime(long time) {
 141         return (TimeUnit.MICROSECONDS.convert(time, TimeUnit.MILLISECONDS)
 142                - WINDOWS_EPOCH_IN_MICROSECONDS) * 10;
 143     }
 144 
 145     public static final long unixToJavaTime(long utime) {
 146         return TimeUnit.MILLISECONDS.convert(utime, TimeUnit.SECONDS);
 147     }
 148 
 149     public static final long javaToUnixTime(long time) {
 150         return TimeUnit.SECONDS.convert(time, TimeUnit.MILLISECONDS);
 151     }
 152 
 153     private static final String regexMetaChars = ".^$+{[]|()";
 154     private static final String globMetaChars = "\\*?[{";
 155     private static boolean isRegexMeta(char c) {
 156         return regexMetaChars.indexOf(c) != -1;
 157     }
 158     private static boolean isGlobMeta(char c) {
 159         return globMetaChars.indexOf(c) != -1;
 160     }
 161     private static char EOL = 0;  //TBD
 162     private static char next(String glob, int i) {
 163         if (i < glob.length()) {
 164             return glob.charAt(i);
 165         }
 166         return EOL;
 167     }
 168 
 169     /*
 170      * Creates a regex pattern from the given glob expression.
 171      *
 172      * @throws  PatternSyntaxException
 173      */
 174     public static String toRegexPattern(String globPattern) {
 175         boolean inGroup = false;
 176         StringBuilder regex = new StringBuilder("^");
 177 
 178         int i = 0;
 179         while (i < globPattern.length()) {
 180             char c = globPattern.charAt(i++);
 181             switch (c) {
 182                 case '\\':
 183                     // escape special characters
 184                     if (i == globPattern.length()) {
 185                         throw new PatternSyntaxException("No character to escape",
 186                                 globPattern, i - 1);
 187                     }
 188                     char next = globPattern.charAt(i++);
 189                     if (isGlobMeta(next) || isRegexMeta(next)) {
 190                         regex.append('\\');
 191                     }
 192                     regex.append(next);
 193                     break;
 194                 case '/':
 195                     regex.append(c);
 196                     break;
 197                 case '[':
 198                     // don't match name separator in class
 199                     regex.append("[[^/]&&[");
 200                     if (next(globPattern, i) == '^') {
 201                         // escape the regex negation char if it appears
 202                         regex.append("\\^");
 203                         i++;
 204                     } else {
 205                         // negation
 206                         if (next(globPattern, i) == '!') {
 207                             regex.append('^');
 208                             i++;
 209                         }
 210                         // hyphen allowed at start
 211                         if (next(globPattern, i) == '-') {
 212                             regex.append('-');
 213                             i++;
 214                         }
 215                     }
 216                     boolean hasRangeStart = false;
 217                     char last = 0;
 218                     while (i < globPattern.length()) {
 219                         c = globPattern.charAt(i++);
 220                         if (c == ']') {
 221                             break;
 222                         }
 223                         if (c == '/') {
 224                             throw new PatternSyntaxException("Explicit 'name separator' in class",
 225                                     globPattern, i - 1);
 226                         }
 227                         // TBD: how to specify ']' in a class?
 228                         if (c == '\\' || c == '[' ||
 229                                 c == '&' && next(globPattern, i) == '&') {
 230                             // escape '\', '[' or "&&" for regex class
 231                             regex.append('\\');
 232                         }
 233                         regex.append(c);
 234 
 235                         if (c == '-') {
 236                             if (!hasRangeStart) {
 237                                 throw new PatternSyntaxException("Invalid range",
 238                                         globPattern, i - 1);
 239                             }
 240                             if ((c = next(globPattern, i++)) == EOL || c == ']') {
 241                                 break;
 242                             }
 243                             if (c < last) {
 244                                 throw new PatternSyntaxException("Invalid range",
 245                                         globPattern, i - 3);
 246                             }
 247                             regex.append(c);
 248                             hasRangeStart = false;
 249                         } else {
 250                             hasRangeStart = true;
 251                             last = c;
 252                         }
 253                     }
 254                     if (c != ']') {
 255                         throw new PatternSyntaxException("Missing ']", globPattern, i - 1);
 256                     }
 257                     regex.append("]]");
 258                     break;
 259                 case '{':
 260                     if (inGroup) {
 261                         throw new PatternSyntaxException("Cannot nest groups",
 262                                 globPattern, i - 1);
 263                     }
 264                     regex.append("(?:(?:");
 265                     inGroup = true;
 266                     break;
 267                 case '}':
 268                     if (inGroup) {
 269                         regex.append("))");
 270                         inGroup = false;
 271                     } else {
 272                         regex.append('}');
 273                     }
 274                     break;
 275                 case ',':
 276                     if (inGroup) {
 277                         regex.append(")|(?:");
 278                     } else {
 279                         regex.append(',');
 280                     }
 281                     break;
 282                 case '*':
 283                     if (next(globPattern, i) == '*') {
 284                         // crosses directory boundaries
 285                         regex.append(".*");
 286                         i++;
 287                     } else {
 288                         // within directory boundary
 289                         regex.append("[^/]*");
 290                     }
 291                     break;
 292                 case '?':
 293                    regex.append("[^/]");
 294                    break;
 295                 default:
 296                     if (isRegexMeta(c)) {
 297                         regex.append('\\');
 298                     }
 299                     regex.append(c);
 300             }
 301         }
 302         if (inGroup) {
 303             throw new PatternSyntaxException("Missing '}", globPattern, i - 1);
 304         }
 305         return regex.append('$').toString();
 306     }
 307 }