1 /*
2  * Copyright (c) 2006, 2013, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 
24 /**
25  * @test
26  * @bug 6480504 6303183
27  * @summary Test that client-provided data in the extra field is written and
28  * read correctly, taking into account the JAR_MAGIC written into the extra
29  * field of the first entry of JAR files.
30  * @author Dave Bristor
31  */
32 
33 import java.io.*;
34 import java.nio.charset.Charset;
35 import java.util.Arrays;
36 import java.util.jar.*;
37 import java.util.zip.*;
38 
39 /**
40  * Tests that the get/set operations on extra data in zip and jar files work
41  * as advertised.  The base class tests ZIP files, the member class
42  * TestJarExtra checks JAR files.
43  */
44 public class TestExtra {
45     static final int JAR_MAGIC = 0xcafe; // private IN JarOutputStream.java
46     static final int TEST_HEADER = 0xbabe;
47 
48     static final Charset ascii = Charset.forName("ASCII");
49 
50     // ZipEntry extra data
51     static final byte[][] extra = new byte[][] {
52         ascii.encode("hello, world").array(),
53         ascii.encode("foo bar").array()
54     };
55 
56     // For naming entries in JAR/ZIP streams
57     int count = 1;
58 
59     // Use byte arrays instead of files
60     ByteArrayOutputStream baos;
61 
62     // JAR/ZIP content written here.
63     ZipOutputStream zos;
64 
realMain(String[] args)65     public static void realMain(String[] args) throws Throwable{
66         new TestExtra().testHeaderPlusData();
67 
68         new TestJarExtra().testHeaderPlusData();
69         new TestJarExtra().testHeaderOnly();
70         new TestJarExtra().testClientJarMagic();
71     }
72 
TestExtra()73     TestExtra() {
74         try {
75             baos = new ByteArrayOutputStream();
76             zos = getOutputStream(baos);
77         } catch (Throwable t) {
78             unexpected(t);
79         }
80     }
81 
82     /** Test that a header + data set by client works. */
testHeaderPlusData()83     void testHeaderPlusData() throws IOException {
84         for (byte[] b : extra) {
85             ZipEntry ze = getEntry();
86             byte[] data = new byte[b.length + 4];
87             set16(data, 0, TEST_HEADER);
88             set16(data, 2, b.length);
89             for (int i = 0; i < b.length; i++) {
90                 data[i + 4] = b[i];
91             }
92             ze.setExtra(data);
93             zos.putNextEntry(ze);
94         }
95         zos.close();
96 
97         ZipInputStream zis = getInputStream();
98 
99         ZipEntry ze = zis.getNextEntry();
100         checkEntry(ze, 0, extra[0].length);
101 
102         ze = zis.getNextEntry();
103         checkEntry(ze, 1, extra[1].length);
104     }
105 
106     /** Test that a header only (i.e., no extra "data") set by client works. */
testHeaderOnly()107     void testHeaderOnly() throws IOException {
108         ZipEntry ze = getEntry();
109         byte[] data = new byte[4];
110         set16(data, 0, TEST_HEADER);
111         set16(data, 2, 0); // Length of data is 0.
112         ze.setExtra(data);
113         zos.putNextEntry(ze);
114 
115         zos.close();
116 
117         ZipInputStream zis = getInputStream();
118 
119         ze = zis.getNextEntry();
120         checkExtra(data, ze.getExtra());
121         checkEntry(ze, 0, 0);
122     }
123 
124     /** Tests the client providing extra data which uses JAR_MAGIC header. */
testClientJarMagic()125     void testClientJarMagic() throws IOException {
126         ZipEntry ze = getEntry();
127         byte[] data = new byte[8];
128 
129         set16(data, 0, TEST_HEADER);
130         set16(data, 2, 0); // Length of data is 0.
131         set16(data, 4, JAR_MAGIC);
132         set16(data, 6, 0); // Length of data is 0.
133 
134         ze.setExtra(data);
135         zos.putNextEntry(ze);
136 
137         zos.close();
138 
139         ZipInputStream zis = getInputStream();
140         ze = zis.getNextEntry();
141         byte[] e = ze.getExtra();
142         checkExtra(data, ze.getExtra());
143         checkEntry(ze, 0, 0);
144     }
145 
146     // check if all "expected" extra fields equal to their
147     // corresponding fields in "extra". The "extra" might have
148     // timestamp fields added by ZOS.
checkExtra(byte[] expected, byte[] extra)149     static void checkExtra(byte[] expected, byte[] extra) {
150         if (expected == null)
151             return;
152         int off = 0;
153         int len = expected.length;
154         while (off + 4 < len) {
155             int tag = get16(expected, off);
156             int sz = get16(expected, off + 2);
157             int off0 = 0;
158             int len0 = extra.length;
159             boolean matched = false;
160             while (off0 + 4 < len0) {
161                 int tag0 = get16(extra, off0);
162                 int sz0 = get16(extra, off0 + 2);
163                 if (tag == tag0 && sz == sz0) {
164                     matched = true;
165                     for (int i = 0; i < sz; i++) {
166                         if (expected[off + i] != extra[off0 +i])
167                             matched = false;
168                     }
169                     break;
170                 }
171                 off0 += (4 + sz0);
172             }
173             if (!matched) {
174                 fail("Expected extra data [tag=" + tag + "sz=" + sz + "] check failed");
175             }
176             off += (4 + sz);
177         }
178     }
179 
180     /** Check that the entry's extra data is correct. */
checkEntry(ZipEntry ze, int count, int dataLength)181     void checkEntry(ZipEntry ze, int count, int dataLength) {
182         byte[] extraData = ze.getExtra();
183         byte[] data = getField(TEST_HEADER, extraData);
184         if (!check(data != null, "unexpected null data for TEST_HEADER")) {
185             return;
186         }
187 
188         if (dataLength == 0) {
189             check(data.length == 0, "unexpected non-zero data length for TEST_HEADER");
190         } else {
191             check(Arrays.equals(extra[count], data),
192                   "failed to get entry " + ze.getName()
193                   + ", expected " + new String(extra[count]) + ", got '" + new String(data) + "'");
194         }
195     }
196 
197     /** Look up descriptor in data, returning corresponding byte[]. */
getField(int descriptor, byte[] data)198     static byte[] getField(int descriptor, byte[] data) {
199         byte[] rc = null;
200         try {
201             int i = 0;
202             while (i < data.length) {
203                 if (get16(data, i) == descriptor) {
204                     int length = get16(data, i + 2);
205                     rc = new byte[length];
206                     for (int j = 0; j < length; j++) {
207                         rc[j] = data[i + 4 + j];
208                     }
209                     return rc;
210                 }
211                 i += get16(data, i + 2) + 4;
212             }
213         } catch (ArrayIndexOutOfBoundsException e) {
214             // descriptor not found
215         }
216         return rc;
217     }
218 
getInputStream()219     ZipInputStream getInputStream() {
220         return new ZipInputStream(
221             new ByteArrayInputStream(baos.toByteArray()));
222     }
223 
getOutputStream(ByteArrayOutputStream baos)224     ZipOutputStream getOutputStream(ByteArrayOutputStream baos) throws IOException {
225         return new ZipOutputStream(baos);
226     }
227 
getInputStream(ByteArrayInputStream bais)228     ZipInputStream getInputStream(ByteArrayInputStream bais) throws IOException {
229         return new ZipInputStream(bais);
230     }
231 
getEntry()232     ZipEntry getEntry() { return new ZipEntry("zip" + count++ + ".txt"); }
233 
234 
get16(byte[] b, int off)235     private static int get16(byte[] b, int off) {
236         return (b[off] & 0xff) | ((b[off+1] & 0xff) << 8);
237     }
238 
set16(byte[] b, int off, int value)239     private static void set16(byte[] b, int off, int value) {
240         b[off+0] = (byte)value;
241         b[off+1] = (byte)(value >> 8);
242     }
243 
244     /** Test extra field of a JAR file. */
245     static class TestJarExtra extends TestExtra {
getOutputStream(ByteArrayOutputStream baos)246         ZipOutputStream getOutputStream(ByteArrayOutputStream baos) throws IOException {
247             return new JarOutputStream(baos);
248         }
249 
getInputStream(ByteArrayInputStream bais)250         ZipInputStream getInputStream(ByteArrayInputStream bais) throws IOException {
251             return new JarInputStream(bais);
252         }
253 
getEntry()254         ZipEntry getEntry() { return new ZipEntry("jar" + count++ + ".txt"); }
255 
checkEntry(ZipEntry ze, int count, int dataLength)256         void checkEntry(ZipEntry ze, int count, int dataLength) {
257             // zeroth entry should have JAR_MAGIC
258             if (count == 0) {
259                 byte[] extraData = ze.getExtra();
260                 byte[] data = getField(JAR_MAGIC, extraData);
261                 if (!check(data != null, "unexpected null data for JAR_MAGIC")) {
262                     check(data.length != 0, "unexpected non-zero data length for JAR_MAGIC");
263                 }
264             }
265             // In a jar file, the first ZipEntry should have both JAR_MAGIC
266             // and the TEST_HEADER, so check that also.
267             super.checkEntry(ze, count, dataLength);
268         }
269     }
270 
271     //--------------------- Infrastructure ---------------------------
272     static volatile int passed = 0, failed = 0;
pass()273     static void pass() {passed++;}
fail()274     static void fail() {failed++; Thread.dumpStack();}
fail(String msg)275     static void fail(String msg) {System.out.println(msg); fail();}
unexpected(Throwable t)276     static void unexpected(Throwable t) {failed++; t.printStackTrace();}
check(boolean cond)277     static void check(boolean cond) {if (cond) pass(); else fail();}
check(boolean cond, String msg)278     static boolean check(boolean cond, String msg) {if (cond) pass(); else fail(msg); return cond; }
equal(Object x, Object y)279     static void equal(Object x, Object y) {
280         if (x == null ? y == null : x.equals(y)) pass();
281         else fail(x + " not equal to " + y);}
main(String[] args)282     public static void main(String[] args) throws Throwable {
283         try {realMain(args);} catch (Throwable t) {unexpected(t);}
284         System.out.println("\nPassed = " + passed + " failed = " + failed);
285         if (failed > 0) throw new Error("Some tests failed");}
286 }
287