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