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
019 package org.apache.commons.compress.archivers.zip;
020
021 import java.io.UnsupportedEncodingException;
022 import java.util.zip.CRC32;
023 import java.util.zip.ZipException;
024
025 import org.apache.commons.compress.utils.CharsetNames;
026
027 /**
028 * A common base class for Unicode extra information extra fields.
029 * @NotThreadSafe
030 */
031 public 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 orginal 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 orginal 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 /** {@inheritDoc} */
131 public byte[] getCentralDirectoryData() {
132 if (data == null) {
133 this.assembleData();
134 }
135 byte[] b = null;
136 if (data != null) {
137 b = new byte[data.length];
138 System.arraycopy(data, 0, b, 0, b.length);
139 }
140 return b;
141 }
142
143 /** {@inheritDoc} */
144 public ZipShort getCentralDirectoryLength() {
145 if (data == null) {
146 assembleData();
147 }
148 return new ZipShort(data.length);
149 }
150
151 /** {@inheritDoc} */
152 public byte[] getLocalFileDataData() {
153 return getCentralDirectoryData();
154 }
155
156 /** {@inheritDoc} */
157 public ZipShort getLocalFileDataLength() {
158 return getCentralDirectoryLength();
159 }
160
161 /** {@inheritDoc} */
162 public void parseFromLocalFileData(byte[] buffer, int offset, int length)
163 throws ZipException {
164
165 if (length < 5) {
166 throw new ZipException("UniCode path extra data must have at least 5 bytes.");
167 }
168
169 int version = buffer[offset];
170
171 if (version != 0x01) {
172 throw new ZipException("Unsupported version [" + version
173 + "] for UniCode path extra data.");
174 }
175
176 nameCRC32 = ZipLong.getValue(buffer, offset + 1);
177 unicodeName = new byte[length - 5];
178 System.arraycopy(buffer, offset + 5, unicodeName, 0, length - 5);
179 data = null;
180 }
181
182 /**
183 * Doesn't do anything special since this class always uses the
184 * same data in central directory and local file data.
185 */
186 public void parseFromCentralDirectoryData(byte[] buffer, int offset,
187 int length)
188 throws ZipException {
189 parseFromLocalFileData(buffer, offset, length);
190 }
191 }