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