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 019package org.apache.commons.compress.archivers.zip; 020 021import java.io.UnsupportedEncodingException; 022import java.util.zip.CRC32; 023import java.util.zip.ZipException; 024 025import org.apache.commons.compress.utils.CharsetNames; 026 027/** 028 * A common base class for Unicode extra information extra fields. 029 * @NotThreadSafe 030 */ 031public abstract class AbstractUnicodeExtraField implements ZipExtraField { 032 private long nameCRC32; 033 private byte[] unicodeName; 034 private byte[] data; 035 036 protected AbstractUnicodeExtraField() { 037 } 038 039 /** 040 * Assemble as unicode extension from the name/comment and 041 * encoding of the original zip entry. 042 * 043 * @param text The file name or comment. 044 * @param bytes The encoded of the filename or comment in the zip 045 * file. 046 * @param off The offset of the encoded filename or comment in 047 * <code>bytes</code>. 048 * @param len The length of the encoded filename or commentin 049 * <code>bytes</code>. 050 */ 051 protected AbstractUnicodeExtraField(String text, byte[] bytes, int off, int len) { 052 CRC32 crc32 = new CRC32(); 053 crc32.update(bytes, off, len); 054 nameCRC32 = crc32.getValue(); 055 056 try { 057 unicodeName = text.getBytes(CharsetNames.UTF_8); 058 } catch (UnsupportedEncodingException e) { 059 throw new RuntimeException("FATAL: UTF-8 encoding not supported.", e); 060 } 061 } 062 063 /** 064 * Assemble as unicode extension from the name/comment and 065 * encoding of the original zip entry. 066 * 067 * @param text The file name or comment. 068 * @param bytes The encoded of the filename or comment in the zip 069 * file. 070 */ 071 protected AbstractUnicodeExtraField(String text, byte[] bytes) { 072 this(text, bytes, 0, bytes.length); 073 } 074 075 private void assembleData() { 076 if (unicodeName == null) { 077 return; 078 } 079 080 data = new byte[5 + unicodeName.length]; 081 // version 1 082 data[0] = 0x01; 083 System.arraycopy(ZipLong.getBytes(nameCRC32), 0, data, 1, 4); 084 System.arraycopy(unicodeName, 0, data, 5, unicodeName.length); 085 } 086 087 /** 088 * @return The CRC32 checksum of the filename or comment as 089 * encoded in the central directory of the zip file. 090 */ 091 public long getNameCRC32() { 092 return nameCRC32; 093 } 094 095 /** 096 * @param nameCRC32 The CRC32 checksum of the filename as encoded 097 * in the central directory of the zip file to set. 098 */ 099 public void setNameCRC32(long nameCRC32) { 100 this.nameCRC32 = nameCRC32; 101 data = null; 102 } 103 104 /** 105 * @return The UTF-8 encoded name. 106 */ 107 public byte[] getUnicodeName() { 108 byte[] b = null; 109 if (unicodeName != null) { 110 b = new byte[unicodeName.length]; 111 System.arraycopy(unicodeName, 0, b, 0, b.length); 112 } 113 return b; 114 } 115 116 /** 117 * @param unicodeName The UTF-8 encoded name to set. 118 */ 119 public void setUnicodeName(byte[] unicodeName) { 120 if (unicodeName != null) { 121 this.unicodeName = new byte[unicodeName.length]; 122 System.arraycopy(unicodeName, 0, this.unicodeName, 0, 123 unicodeName.length); 124 } else { 125 this.unicodeName = null; 126 } 127 data = null; 128 } 129 130 public byte[] getCentralDirectoryData() { 131 if (data == null) { 132 this.assembleData(); 133 } 134 byte[] b = null; 135 if (data != null) { 136 b = new byte[data.length]; 137 System.arraycopy(data, 0, b, 0, b.length); 138 } 139 return b; 140 } 141 142 public ZipShort getCentralDirectoryLength() { 143 if (data == null) { 144 assembleData(); 145 } 146 return new ZipShort(data != null ? data.length : 0); 147 } 148 149 public byte[] getLocalFileDataData() { 150 return getCentralDirectoryData(); 151 } 152 153 public ZipShort getLocalFileDataLength() { 154 return getCentralDirectoryLength(); 155 } 156 157 public void parseFromLocalFileData(byte[] buffer, int offset, int length) 158 throws ZipException { 159 160 if (length < 5) { 161 throw new ZipException("UniCode path extra data must have at least 5 bytes."); 162 } 163 164 int version = buffer[offset]; 165 166 if (version != 0x01) { 167 throw new ZipException("Unsupported version [" + version 168 + "] for UniCode path extra data."); 169 } 170 171 nameCRC32 = ZipLong.getValue(buffer, offset + 1); 172 unicodeName = new byte[length - 5]; 173 System.arraycopy(buffer, offset + 5, unicodeName, 0, length - 5); 174 data = null; 175 } 176 177 /** 178 * Doesn't do anything special since this class always uses the 179 * same data in central directory and local file data. 180 */ 181 public void parseFromCentralDirectoryData(byte[] buffer, int offset, 182 int length) 183 throws ZipException { 184 parseFromLocalFileData(buffer, offset, length); 185 } 186}