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
33import java.io.*;
34import java.nio.charset.Charset;
35import java.util.Arrays;
36import java.util.jar.*;
37import 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 */
44public 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
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
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. */
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. */
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. */
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.
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. */
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[]. */
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
219    ZipInputStream getInputStream() {
220        return new ZipInputStream(
221            new ByteArrayInputStream(baos.toByteArray()));
222    }
223
224    ZipOutputStream getOutputStream(ByteArrayOutputStream baos) throws IOException {
225        return new ZipOutputStream(baos);
226    }
227
228    ZipInputStream getInputStream(ByteArrayInputStream bais) throws IOException {
229        return new ZipInputStream(bais);
230    }
231
232    ZipEntry getEntry() { return new ZipEntry("zip" + count++ + ".txt"); }
233
234
235    private static int get16(byte[] b, int off) {
236        return (b[off] & 0xff) | ((b[off+1] & 0xff) << 8);
237    }
238
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 {
246        ZipOutputStream getOutputStream(ByteArrayOutputStream baos) throws IOException {
247            return new JarOutputStream(baos);
248        }
249
250        ZipInputStream getInputStream(ByteArrayInputStream bais) throws IOException {
251            return new JarInputStream(bais);
252        }
253
254        ZipEntry getEntry() { return new ZipEntry("jar" + count++ + ".txt"); }
255
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;
273    static void pass() {passed++;}
274    static void fail() {failed++; Thread.dumpStack();}
275    static void fail(String msg) {System.out.println(msg); fail();}
276    static void unexpected(Throwable t) {failed++; t.printStackTrace();}
277    static void check(boolean cond) {if (cond) pass(); else fail();}
278    static boolean check(boolean cond, String msg) {if (cond) pass(); else fail(msg); return cond; }
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);}
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