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