001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 * 017 */ 018 package org.apache.commons.compress.archivers.zip; 019 020 import java.io.IOException; 021 import java.util.Calendar; 022 import java.util.Date; 023 import java.util.zip.CRC32; 024 025 /** 026 * Utility class for handling DOS and Java time conversions. 027 * @Immutable 028 */ 029 public abstract class ZipUtil { 030 /** 031 * Smallest date/time ZIP can handle. 032 */ 033 private static final byte[] DOS_TIME_MIN = ZipLong.getBytes(0x00002100L); 034 035 /** 036 * Convert a Date object to a DOS date/time field. 037 * @param time the <code>Date</code> to convert 038 * @return the date as a <code>ZipLong</code> 039 */ 040 public static ZipLong toDosTime(Date time) { 041 return new ZipLong(toDosTime(time.getTime())); 042 } 043 044 /** 045 * Convert a Date object to a DOS date/time field. 046 * 047 * <p>Stolen from InfoZip's <code>fileio.c</code></p> 048 * @param t number of milliseconds since the epoch 049 * @return the date as a byte array 050 */ 051 public static byte[] toDosTime(long t) { 052 Calendar c = Calendar.getInstance(); 053 c.setTimeInMillis(t); 054 055 int year = c.get(Calendar.YEAR); 056 if (year < 1980) { 057 return (byte[]) DOS_TIME_MIN.clone(); // stop callers from changing the array 058 } 059 int month = c.get(Calendar.MONTH) + 1; 060 long value = ((year - 1980) << 25) 061 | (month << 21) 062 | (c.get(Calendar.DAY_OF_MONTH) << 16) 063 | (c.get(Calendar.HOUR_OF_DAY) << 11) 064 | (c.get(Calendar.MINUTE) << 5) 065 | (c.get(Calendar.SECOND) >> 1); 066 return ZipLong.getBytes(value); 067 } 068 069 /** 070 * Assumes a negative integer really is a positive integer that 071 * has wrapped around and re-creates the original value. 072 * @param i the value to treat as unsigned int. 073 * @return the unsigned int as a long. 074 */ 075 public static long adjustToLong(int i) { 076 if (i < 0) { 077 return 2 * ((long) Integer.MAX_VALUE) + 2 + i; 078 } else { 079 return i; 080 } 081 } 082 083 /** 084 * Convert a DOS date/time field to a Date object. 085 * 086 * @param zipDosTime contains the stored DOS time. 087 * @return a Date instance corresponding to the given time. 088 */ 089 public static Date fromDosTime(ZipLong zipDosTime) { 090 long dosTime = zipDosTime.getValue(); 091 return new Date(dosToJavaTime(dosTime)); 092 } 093 094 /** 095 * Converts DOS time to Java time (number of milliseconds since 096 * epoch). 097 */ 098 public static long dosToJavaTime(long dosTime) { 099 Calendar cal = Calendar.getInstance(); 100 // CheckStyle:MagicNumberCheck OFF - no point 101 cal.set(Calendar.YEAR, (int) ((dosTime >> 25) & 0x7f) + 1980); 102 cal.set(Calendar.MONTH, (int) ((dosTime >> 21) & 0x0f) - 1); 103 cal.set(Calendar.DATE, (int) (dosTime >> 16) & 0x1f); 104 cal.set(Calendar.HOUR_OF_DAY, (int) (dosTime >> 11) & 0x1f); 105 cal.set(Calendar.MINUTE, (int) (dosTime >> 5) & 0x3f); 106 cal.set(Calendar.SECOND, (int) (dosTime << 1) & 0x3e); 107 // CheckStyle:MagicNumberCheck ON 108 return cal.getTime().getTime(); 109 } 110 111 /** 112 * If the entry has Unicode*ExtraFields and the CRCs of the 113 * names/comments match those of the extra fields, transfer the 114 * known Unicode values from the extra field. 115 */ 116 static void setNameAndCommentFromExtraFields(ZipArchiveEntry ze, 117 byte[] originalNameBytes, 118 byte[] commentBytes) { 119 UnicodePathExtraField name = (UnicodePathExtraField) 120 ze.getExtraField(UnicodePathExtraField.UPATH_ID); 121 String originalName = ze.getName(); 122 String newName = getUnicodeStringIfOriginalMatches(name, 123 originalNameBytes); 124 if (newName != null && !originalName.equals(newName)) { 125 ze.setName(newName); 126 } 127 128 if (commentBytes != null && commentBytes.length > 0) { 129 UnicodeCommentExtraField cmt = (UnicodeCommentExtraField) 130 ze.getExtraField(UnicodeCommentExtraField.UCOM_ID); 131 String newComment = 132 getUnicodeStringIfOriginalMatches(cmt, commentBytes); 133 if (newComment != null) { 134 ze.setComment(newComment); 135 } 136 } 137 } 138 139 /** 140 * If the stored CRC matches the one of the given name, return the 141 * Unicode name of the given field. 142 * 143 * <p>If the field is null or the CRCs don't match, return null 144 * instead.</p> 145 */ 146 private static 147 String getUnicodeStringIfOriginalMatches(AbstractUnicodeExtraField f, 148 byte[] orig) { 149 if (f != null) { 150 CRC32 crc32 = new CRC32(); 151 crc32.update(orig); 152 long origCRC32 = crc32.getValue(); 153 154 if (origCRC32 == f.getNameCRC32()) { 155 try { 156 return ZipEncodingHelper 157 .UTF8_ZIP_ENCODING.decode(f.getUnicodeName()); 158 } catch (IOException ex) { 159 // UTF-8 unsupported? should be impossible the 160 // Unicode*ExtraField must contain some bad bytes 161 162 // TODO log this anywhere? 163 return null; 164 } 165 } 166 } 167 return null; 168 } 169 170 171 }