View Javadoc
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  
20  package org.apache.commons.compress.archivers.zip;
21  
22  import static org.junit.jupiter.api.Assertions.assertEquals;
23  import static org.junit.jupiter.api.Assertions.assertThrows;
24  import static org.junit.jupiter.api.Assertions.assertTrue;
25  
26  import java.util.zip.ZipException;
27  
28  import org.junit.jupiter.api.BeforeEach;
29  import org.junit.jupiter.api.Test;
30  
31  /**
32   * JUnit tests for org.apache.commons.compress.archivers.zip.ExtraFieldUtils.
33   */
34  class ExtraFieldUtilsTest implements UnixStat {
35  
36      public static class AiobThrowingExtraField implements ZipExtraField {
37          static final int LENGTH = 4;
38  
39          @Override
40          public byte[] getCentralDirectoryData() {
41              return getLocalFileDataData();
42          }
43  
44          @Override
45          public ZipShort getCentralDirectoryLength() {
46              return getLocalFileDataLength();
47          }
48  
49          @Override
50          public ZipShort getHeaderId() {
51              return AIOB_HEADER;
52          }
53  
54          @Override
55          public byte[] getLocalFileDataData() {
56              return new byte[LENGTH];
57          }
58  
59          @Override
60          public ZipShort getLocalFileDataLength() {
61              return new ZipShort(LENGTH);
62          }
63  
64          @Override
65          public void parseFromCentralDirectoryData(final byte[] buffer, final int offset, final int length) {
66              parseFromLocalFileData(buffer, offset, length);
67          }
68  
69          @Override
70          public void parseFromLocalFileData(final byte[] buffer, final int offset, final int length) {
71              throw new ArrayIndexOutOfBoundsException();
72          }
73      }
74  
75      /**
76       * Header-ID of a ZipExtraField not supported by Commons Compress.
77       *
78       * <p>
79       * Used to be ZipShort(1) but this is the ID of the Zip64 extra field.
80       * </p>
81       */
82      static final ZipShort UNRECOGNIZED_HEADER = new ZipShort(0x5555);
83  
84      /**
85       * Header-ID of a ZipExtraField not supported by Commons Compress used for the ArrayIndexOutOfBoundsTest.
86       */
87      static final ZipShort AIOB_HEADER = new ZipShort(0x1000);
88      private AsiExtraField a;
89      private UnrecognizedExtraField dummy;
90      private byte[] data;
91  
92      private byte[] aLocal;
93  
94      @BeforeEach
95      public void setUp() {
96          a = new AsiExtraField();
97          a.setMode(0755);
98          a.setDirectory(true);
99          dummy = new UnrecognizedExtraField();
100         dummy.setHeaderId(UNRECOGNIZED_HEADER);
101         dummy.setLocalFileDataData(new byte[] { 0 });
102         dummy.setCentralDirectoryData(new byte[] { 0 });
103 
104         aLocal = a.getLocalFileDataData();
105         final byte[] dummyLocal = dummy.getLocalFileDataData();
106         data = new byte[4 + aLocal.length + 4 + dummyLocal.length];
107         System.arraycopy(a.getHeaderId().getBytes(), 0, data, 0, 2);
108         System.arraycopy(a.getLocalFileDataLength().getBytes(), 0, data, 2, 2);
109         System.arraycopy(aLocal, 0, data, 4, aLocal.length);
110         System.arraycopy(dummy.getHeaderId().getBytes(), 0, data, 4 + aLocal.length, 2);
111         System.arraycopy(dummy.getLocalFileDataLength().getBytes(), 0, data, 4 + aLocal.length + 2, 2);
112         System.arraycopy(dummyLocal, 0, data, 4 + aLocal.length + 4, dummyLocal.length);
113 
114     }
115 
116     /**
117      * Test merge methods
118      */
119     @Test
120     void testMerge() {
121         final byte[] local = ExtraFieldUtils.mergeLocalFileDataData(new ZipExtraField[] { a, dummy });
122         assertEquals(data.length, local.length, "local length");
123         for (int i = 0; i < local.length; i++) {
124             assertEquals(data[i], local[i], "local byte " + i);
125         }
126 
127         final byte[] dummyCentral = dummy.getCentralDirectoryData();
128         final byte[] data2 = new byte[4 + aLocal.length + 4 + dummyCentral.length];
129         System.arraycopy(data, 0, data2, 0, 4 + aLocal.length + 2);
130         System.arraycopy(dummy.getCentralDirectoryLength().getBytes(), 0, data2, 4 + aLocal.length + 2, 2);
131         System.arraycopy(dummyCentral, 0, data2, 4 + aLocal.length + 4, dummyCentral.length);
132 
133         final byte[] central = ExtraFieldUtils.mergeCentralDirectoryData(new ZipExtraField[] { a, dummy });
134         assertEquals(data2.length, central.length, "central length");
135         for (int i = 0; i < central.length; i++) {
136             assertEquals(data2[i], central[i], "central byte " + i);
137         }
138 
139     }
140 
141     @Test
142     void testMergeWithUnparseableData() throws Exception {
143         final ZipExtraField d = new UnparseableExtraFieldData();
144         final byte[] b = UNRECOGNIZED_HEADER.getBytes();
145         d.parseFromLocalFileData(new byte[] { b[0], b[1], 1, 0 }, 0, 4);
146         final byte[] local = ExtraFieldUtils.mergeLocalFileDataData(new ZipExtraField[] { a, d });
147         assertEquals(data.length - 1, local.length, "local length");
148         for (int i = 0; i < local.length; i++) {
149             assertEquals(data[i], local[i], "local byte " + i);
150         }
151 
152         final byte[] dCentral = d.getCentralDirectoryData();
153         final byte[] data2 = new byte[4 + aLocal.length + dCentral.length];
154         System.arraycopy(data, 0, data2, 0, 4 + aLocal.length + 2);
155         System.arraycopy(dCentral, 0, data2, 4 + aLocal.length, dCentral.length);
156 
157         final byte[] central = ExtraFieldUtils.mergeCentralDirectoryData(new ZipExtraField[] { a, d });
158         assertEquals(data2.length, central.length, "central length");
159         for (int i = 0; i < central.length; i++) {
160             assertEquals(data2[i], central[i], "central byte " + i);
161         }
162 
163     }
164 
165     /**
166      * test parser.
167      */
168     @Test
169     void testParse() throws Exception {
170         final ZipExtraField[] ze = ExtraFieldUtils.parse(data);
171         assertEquals(2, ze.length, "number of fields");
172         assertTrue(ze[0] instanceof AsiExtraField, "type field 1");
173         assertEquals(040755, ((AsiExtraField) ze[0]).getMode(), "mode field 1");
174         assertTrue(ze[1] instanceof UnrecognizedExtraField, "type field 2");
175         assertEquals(1, ze[1].getLocalFileDataLength().getValue(), "data length field 2");
176 
177         final byte[] data2 = new byte[data.length - 1];
178         System.arraycopy(data, 0, data2, 0, data2.length);
179         final Exception e = assertThrows(Exception.class, () -> ExtraFieldUtils.parse(data2), "data should be invalid");
180         assertEquals("Bad extra field starting at " + (4 + aLocal.length) + ".  Block length of 1 bytes exceeds remaining data of 0 bytes.", e.getMessage(),
181                 "message");
182     }
183 
184     @Test
185     void testParseCentral() throws Exception {
186         final ZipExtraField[] ze = ExtraFieldUtils.parse(data, false);
187         assertEquals(2, ze.length, "number of fields");
188         assertTrue(ze[0] instanceof AsiExtraField, "type field 1");
189         assertEquals(040755, ((AsiExtraField) ze[0]).getMode(), "mode field 1");
190         assertTrue(ze[1] instanceof UnrecognizedExtraField, "type field 2");
191         assertEquals(1, ze[1].getCentralDirectoryLength().getValue(), "data length field 2");
192 
193     }
194 
195     @Test
196     void testParseTurnsArrayIndexOutOfBoundsIntoZipException() {
197         ExtraFieldUtils.register(AiobThrowingExtraField.class);
198         final AiobThrowingExtraField f = new AiobThrowingExtraField();
199         final byte[] d = new byte[4 + AiobThrowingExtraField.LENGTH];
200         System.arraycopy(f.getHeaderId().getBytes(), 0, d, 0, 2);
201         System.arraycopy(f.getLocalFileDataLength().getBytes(), 0, d, 2, 2);
202         System.arraycopy(f.getLocalFileDataData(), 0, d, 4, AiobThrowingExtraField.LENGTH);
203         final ZipException e = assertThrows(ZipException.class, () -> ExtraFieldUtils.parse(d), "data should be invalid");
204         assertEquals("Failed to parse corrupt ZIP extra field of type 1000", e.getMessage(), "message");
205     }
206 
207     @Test
208     void testParseWithRead() throws Exception {
209         ZipExtraField[] ze = ExtraFieldUtils.parse(data, true, ExtraFieldUtils.UnparseableExtraField.READ);
210         assertEquals(2, ze.length, "number of fields");
211         assertTrue(ze[0] instanceof AsiExtraField, "type field 1");
212         assertEquals(040755, ((AsiExtraField) ze[0]).getMode(), "mode field 1");
213         assertTrue(ze[1] instanceof UnrecognizedExtraField, "type field 2");
214         assertEquals(1, ze[1].getLocalFileDataLength().getValue(), "data length field 2");
215 
216         final byte[] data2 = new byte[data.length - 1];
217         System.arraycopy(data, 0, data2, 0, data2.length);
218         ze = ExtraFieldUtils.parse(data2, true, ExtraFieldUtils.UnparseableExtraField.READ);
219         assertEquals(2, ze.length, "number of fields");
220         assertTrue(ze[0] instanceof AsiExtraField, "type field 1");
221         assertEquals(040755, ((AsiExtraField) ze[0]).getMode(), "mode field 1");
222         assertTrue(ze[1] instanceof UnparseableExtraFieldData, "type field 2");
223         assertEquals(4, ze[1].getLocalFileDataLength().getValue(), "data length field 2");
224         for (int i = 0; i < 4; i++) {
225             assertEquals(data2[data.length - 5 + i], ze[1].getLocalFileDataData()[i], "byte number " + i);
226         }
227     }
228 
229     @Test
230     void testParseWithSkip() throws Exception {
231         ZipExtraField[] ze = ExtraFieldUtils.parse(data, true, ExtraFieldUtils.UnparseableExtraField.SKIP);
232         assertEquals(2, ze.length, "number of fields");
233         assertTrue(ze[0] instanceof AsiExtraField, "type field 1");
234         assertEquals(040755, ((AsiExtraField) ze[0]).getMode(), "mode field 1");
235         assertTrue(ze[1] instanceof UnrecognizedExtraField, "type field 2");
236         assertEquals(1, ze[1].getLocalFileDataLength().getValue(), "data length field 2");
237 
238         final byte[] data2 = new byte[data.length - 1];
239         System.arraycopy(data, 0, data2, 0, data2.length);
240         ze = ExtraFieldUtils.parse(data2, true, ExtraFieldUtils.UnparseableExtraField.SKIP);
241         assertEquals(1, ze.length, "number of fields");
242         assertTrue(ze[0] instanceof AsiExtraField, "type field 1");
243         assertEquals(040755, ((AsiExtraField) ze[0]).getMode(), "mode field 1");
244     }
245 }