1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * https://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.commons.compress.archivers.zip;
20
21 import static org.apache.commons.compress.archivers.zip.ZipUtil.reverse;
22 import static org.apache.commons.compress.archivers.zip.ZipUtil.unsignedIntToSignedByte;
23
24 import java.io.Serializable;
25 import java.math.BigInteger;
26 import java.util.Arrays;
27 import java.util.zip.ZipException;
28
29 import org.apache.commons.compress.utils.ByteUtils;
30 import org.apache.commons.lang3.ArrayUtils;
31
32 /**
33 * An extra field that stores Unix UID/GID data (owner & group ownership) for a given ZIP entry. We're using the field definition given in Info-Zip's source
34 * archive: zip-3.0.tar.gz/proginfo/extrafld.txt
35 *
36 * <pre>
37 * Local-header version:
38 *
39 * Value Size Description
40 * ----- ---- -----------
41 * 0x7875 Short tag for this extra block type ("ux")
42 * TSize Short total data size for this block
43 * Version 1 byte version of this extra field, currently 1
44 * UIDSize 1 byte Size of UID field
45 * UID Variable UID for this entry (little-endian)
46 * GIDSize 1 byte Size of GID field
47 * GID Variable GID for this entry (little-endian)
48 *
49 * Central-header version:
50 *
51 * Value Size Description
52 * ----- ---- -----------
53 * 0x7855 Short tag for this extra block type ("Ux")
54 * TSize Short total data size for this block (0)
55 * </pre>
56 *
57 * @since 1.5
58 */
59 public class X7875_NewUnix implements ZipExtraField, Cloneable, Serializable {
60 static final ZipShort HEADER_ID = new ZipShort(0x7875);
61 private static final ZipShort ZERO = new ZipShort(0);
62 private static final BigInteger ONE_THOUSAND = BigInteger.valueOf(1000);
63 private static final long serialVersionUID = 1L;
64
65 /**
66 * Not really for external usage, but marked "package" visibility to help us JUnit it. Trims a byte array of leading zeroes while also enforcing a minimum
67 * length, and thus it really trims AND pads at the same time.
68 *
69 * @param array byte[] array to trim & pad.
70 * @return trimmed & padded byte[] array.
71 */
72 static byte[] trimLeadingZeroesForceMinLength(final byte[] array) {
73 if (array == null) {
74 return null;
75 }
76
77 int pos = 0;
78 for (final byte b : array) {
79 if (b != 0) {
80 break;
81 }
82 pos++;
83 }
84
85 /*
86 * I agonized over my choice of MIN_LENGTH=1. Here's the situation: InfoZip (the tool I am using to test interop) always sets these to length=4. And so
87 * a UID of 0 (typically root) for example is encoded as {4,0,0,0,0} (len=4, 32 bits of zero), when it could just as easily be encoded as {1,0} (len=1,
88 * 8 bits of zero) according to the spec.
89 *
90 * In the end I decided on MIN_LENGTH=1 for four reasons:
91 *
92 * 1.) We are adhering to the spec as far as I can tell, and so a consumer that cannot parse this is broken.
93 *
94 * 2.) Fundamentally, ZIP files are about shrinking things, so let's save a few bytes per entry while we can.
95 *
96 * 3.) Of all the people creating ZIP files using commons- compress, how many care about Unix UID/GID attributes of the files they store? (for example,
97 * I am probably thinking way too hard about this and no one cares!)
98 *
99 * 4.) InfoZip's tool, even though it carefully stores every UID/GID for every file zipped on a Unix machine (by default) currently appears unable to
100 * ever restore UID/GID. unzip -X has no effect on my machine, even when run as root!!!!
101 *
102 * And thus it is decided: MIN_LENGTH=1.
103 *
104 * If anyone runs into interop problems from this, feel free to set it to MIN_LENGTH=4 at some future time, and then we will behave exactly like InfoZip
105 * (requires changes to unit tests, though).
106 *
107 * And I am sorry that the time you spent reading this comment is now gone, and you can never have it back.
108 */
109 final int MIN_LENGTH = 1;
110
111 final byte[] trimmedArray = new byte[Math.max(MIN_LENGTH, array.length - pos)];
112 final int startPos = trimmedArray.length - (array.length - pos);
113 System.arraycopy(array, pos, trimmedArray, startPos, trimmedArray.length - startPos);
114 return trimmedArray;
115 }
116
117 private int version = 1; // always '1' according to current info-zip spec.
118 // BigInteger helps us with little-endian / big-endian conversions.
119 // (thanks to BigInteger.toByteArray() and a reverse() method we created).
120 // Also, the spec theoretically allows UID/GID up to 255 bytes long!
121 //
122 // NOTE: equals() and hashCode() currently assume these can never be null.
123 private BigInteger uid;
124
125 private BigInteger gid;
126
127 /**
128 * Constructor for X7875_NewUnix.
129 */
130 public X7875_NewUnix() {
131 reset();
132 }
133
134 @Override
135 public Object clone() throws CloneNotSupportedException {
136 return super.clone();
137 }
138
139 @Override
140 public boolean equals(final Object o) {
141 if (o instanceof X7875_NewUnix) {
142 final X7875_NewUnix xf = (X7875_NewUnix) o;
143 // We assume uid and gid can never be null.
144 return version == xf.version && uid.equals(xf.uid) && gid.equals(xf.gid);
145 }
146 return false;
147 }
148
149 /**
150 * The actual data to put into central directory data - without Header-ID or length specifier.
151 *
152 * @return get the data
153 */
154 @Override
155 public byte[] getCentralDirectoryData() {
156 return ByteUtils.EMPTY_BYTE_ARRAY;
157 }
158
159 /**
160 * Length of the extra field in the central directory data - without Header-ID or length specifier.
161 *
162 * @return a {@code ZipShort} for the length of the data of this extra field
163 */
164 @Override
165 public ZipShort getCentralDirectoryLength() {
166 return ZERO;
167 }
168
169 /**
170 * Gets the GID as a long. GID is typically a 32 bit unsigned value on most Unix systems, so we return a long to avoid integer overflow into the negatives
171 * in case values above and including 2^31 are being used.
172 *
173 * @return the GID value.
174 */
175 public long getGID() {
176 return ZipUtil.toLong(gid);
177 }
178
179 /**
180 * The Header-ID.
181 *
182 * @return the value for the header id for this extrafield
183 */
184 @Override
185 public ZipShort getHeaderId() {
186 return HEADER_ID;
187 }
188
189 /**
190 * The actual data to put into local file data - without Header-ID or length specifier.
191 *
192 * @return get the data
193 */
194 @Override
195 public byte[] getLocalFileDataData() {
196 byte[] uidBytes = uid.toByteArray();
197 byte[] gidBytes = gid.toByteArray();
198 // BigInteger might prepend a leading-zero to force a positive representation
199 // (for example, so that the sign-bit is set to zero). We need to remove that
200 // before sending the number over the wire.
201 uidBytes = trimLeadingZeroesForceMinLength(uidBytes);
202 final int uidBytesLen = ArrayUtils.getLength(uidBytes);
203 gidBytes = trimLeadingZeroesForceMinLength(gidBytes);
204 final int gidBytesLen = ArrayUtils.getLength(gidBytes);
205 // Couldn't bring myself to just call getLocalFileDataLength() when we've
206 // already got the arrays right here. Yeah, yeah, I know, premature
207 // optimization is the root of all...
208 //
209 // The 3 comes from: version=1 + uidsize=1 + gidsize=1
210 final byte[] data = new byte[3 + uidBytesLen + gidBytesLen];
211 // reverse() switches byte array from big-endian to little-endian.
212 if (uidBytes != null) {
213 reverse(uidBytes);
214 }
215 if (gidBytes != null) {
216 reverse(gidBytes);
217 }
218 int pos = 0;
219 data[pos++] = unsignedIntToSignedByte(version);
220 data[pos++] = unsignedIntToSignedByte(uidBytesLen);
221 if (uidBytes != null) {
222 System.arraycopy(uidBytes, 0, data, pos, uidBytesLen);
223 }
224 pos += uidBytesLen;
225 data[pos++] = unsignedIntToSignedByte(gidBytesLen);
226 if (gidBytes != null) {
227 System.arraycopy(gidBytes, 0, data, pos, gidBytesLen);
228 }
229 return data;
230 }
231
232 /**
233 * Length of the extra field in the local file data - without Header-ID or length specifier.
234 *
235 * @return a {@code ZipShort} for the length of the data of this extra field
236 */
237 @Override
238 public ZipShort getLocalFileDataLength() {
239 byte[] b = trimLeadingZeroesForceMinLength(uid.toByteArray());
240 final int uidSize = ArrayUtils.getLength(b);
241 b = trimLeadingZeroesForceMinLength(gid.toByteArray());
242 final int gidSize = ArrayUtils.getLength(b);
243 // The 3 comes from: version=1 + uidsize=1 + gidsize=1
244 return new ZipShort(3 + uidSize + gidSize);
245 }
246
247 /**
248 * Gets the UID as a long. UID is typically a 32 bit unsigned value on most Unix systems, so we return a long to avoid integer overflow into the negatives
249 * in case values above and including 2^31 are being used.
250 *
251 * @return the UID value.
252 */
253 public long getUID() {
254 return ZipUtil.toLong(uid);
255 }
256
257 @Override
258 public int hashCode() {
259 int hc = -1234567 * version;
260 // Since most UIDs and GIDs are below 65,536, this is (hopefully!)
261 // a nice way to make sure typical UID and GID values impact the hash
262 // as much as possible.
263 hc ^= Integer.rotateLeft(uid.hashCode(), 16);
264 hc ^= gid.hashCode();
265 return hc;
266 }
267
268 /**
269 * Doesn't do anything since this class doesn't store anything inside the central directory.
270 */
271 @Override
272 public void parseFromCentralDirectoryData(final byte[] buffer, final int offset, final int length) throws ZipException {
273 }
274
275 /**
276 * Populate data from this array as if it was in local file data.
277 *
278 * @param data an array of bytes
279 * @param offset the start offset
280 * @param length the number of bytes in the array from offset
281 * @throws java.util.zip.ZipException on error
282 */
283 @Override
284 public void parseFromLocalFileData(final byte[] data, int offset, final int length) throws ZipException {
285 reset();
286 if (length < 3) {
287 throw new ZipException("X7875_NewUnix length is too short, only " + length + " bytes");
288 }
289 this.version = Byte.toUnsignedInt(data[offset++]);
290 final int uidSize = Byte.toUnsignedInt(data[offset++]);
291 if (uidSize + 3 > length) {
292 throw new ZipException("X7875_NewUnix invalid: uidSize " + uidSize + " doesn't fit into " + length + " bytes");
293 }
294 final byte[] uidBytes = Arrays.copyOfRange(data, offset, offset + uidSize);
295 offset += uidSize;
296 this.uid = new BigInteger(1, reverse(uidBytes)); // sign-bit forced positive
297
298 final int gidSize = Byte.toUnsignedInt(data[offset++]);
299 if (uidSize + 3 + gidSize > length) {
300 throw new ZipException("X7875_NewUnix invalid: gidSize " + gidSize + " doesn't fit into " + length + " bytes");
301 }
302 final byte[] gidBytes = Arrays.copyOfRange(data, offset, offset + gidSize);
303 this.gid = new BigInteger(1, reverse(gidBytes)); // sign-bit forced positive
304 }
305
306 /**
307 * Reset state back to newly constructed state. Helps us make sure parse() calls always generate clean results.
308 */
309 private void reset() {
310 // Typical UID/GID of the first non-root user created on a Unix system.
311 uid = ONE_THOUSAND;
312 gid = ONE_THOUSAND;
313 }
314
315 /**
316 * Sets the GID.
317 *
318 * @param l GID value to set on this extra field.
319 */
320 public void setGID(final long l) {
321 this.gid = ZipUtil.longToBig(l);
322 }
323
324 /**
325 * Sets the UID.
326 *
327 * @param l UID value to set on this extra field.
328 */
329 public void setUID(final long l) {
330 this.uid = ZipUtil.longToBig(l);
331 }
332
333 /**
334 * Returns a String representation of this class useful for debugging purposes.
335 *
336 * @return A String representation of this class useful for debugging purposes.
337 */
338 @Override
339 public String toString() {
340 return "0x7875 Zip Extra Field: UID=" + uid + " GID=" + gid;
341 }
342 }