1 /*
2  * Copyright (c) 2005, 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 /* @test
25  * @bug 4770745 6234507 6303183 8048990
26  * @summary test a variety of zip file entries
27  * @author Martin Buchholz
28  * @key randomness
29  */
30 
31 import java.util.*;
32 import java.util.zip.*;
33 import java.util.jar.*;
34 import java.io.*;
35 
36 public class Assortment {
37     static int passed = 0, failed = 0;
38 
fail(String msg)39     static void fail(String msg) {
40         failed++;
41         new Exception(msg).printStackTrace();
42     }
43 
unexpected(Throwable t)44     static void unexpected(Throwable t) {
45         failed++;
46         t.printStackTrace();
47     }
48 
check(boolean condition, String msg)49     static void check(boolean condition, String msg) {
50         if (! condition)
51             fail(msg);
52     }
53 
check(boolean condition)54     static void check(boolean condition) {
55         check(condition, "Something's wrong");
56     }
57 
get16(byte b[], int off)58     static final int get16(byte b[], int off) {
59         return Byte.toUnsignedInt(b[off]) | (Byte.toUnsignedInt(b[off+1]) << 8);
60     }
61 
62     // check if all "expected" extra fields equal to their
63     // corresponding fields in "extra". The "extra" might have
64     // timestamp fields added by ZOS.
equalsExtraData(byte[] expected, byte[] extra)65     static boolean equalsExtraData(byte[] expected, byte[] extra) {
66         if (expected == null)
67             return true;
68         int off = 0;
69         int len = expected.length;
70         while (off + 4 < len) {
71             int tag = get16(expected, off);
72             int sz = get16(expected, off + 2);
73             int off0 = 0;
74             int len0 = extra.length;
75             boolean matched = false;
76             while (off0 + 4 < len0) {
77                 int tag0 = get16(extra, off0);
78                 int sz0 = get16(extra, off0 + 2);
79                 if (tag == tag0 && sz == sz0) {
80                     matched = true;
81                     for (int i = 0; i < sz; i++) {
82                         if (expected[off + i] != extra[off0 +i])
83                             matched = false;
84                     }
85                     break;
86                 }
87                 off0 += (4 + sz0);
88             }
89             if (!matched)
90                 return false;
91             off += (4 + sz);
92         }
93         return true;
94     }
95 
96     private static class Entry {
97         private String name;
98         private int    method;
99         private byte[] data;
100         private byte[] extra;
101         private String comment;
102 
Entry(String name, int method, byte[] data, byte[] extra, String comment)103         Entry(String name,
104               int    method,
105               byte[] data,
106               byte[] extra,
107               String comment) {
108             this.name    = name;
109             this.method  = method;
110             this.data    = data;
111             this.extra   = extra;
112             this.comment = comment;
113         }
114 
write(ZipOutputStream s)115         void write(ZipOutputStream s) throws Exception {
116             ZipEntry e = new ZipEntry(name);
117             CRC32 crc32 = new CRC32();
118             e.setMethod(method);
119             if (method == ZipEntry.STORED) {
120                 e.setSize(data == null ? 0 : data.length);
121                 crc32.reset();
122                 if (data != null) crc32.update(data);
123                 e.setCrc(crc32.getValue());
124             } else {
125                 e.setSize(0);
126                 e.setCrc(0);
127             }
128             if (comment != null) e.setComment(comment);
129             if (extra   != null) e.setExtra(extra);
130             s.putNextEntry(e);
131             if (data != null) s.write(data);
132         }
133 
getData(ZipFile f, ZipEntry e)134         byte[] getData(ZipFile f, ZipEntry e) throws Exception {
135             byte[] fdata = new byte[(int)e.getSize()];
136             InputStream is = f.getInputStream(e);
137             is.read(fdata);
138             return fdata;
139         }
140 
verify(ZipFile f, ZipEntry e)141         void verify(ZipFile f, ZipEntry e) throws Exception {
142             verify(e, getData(f, e));
143         }
144 
verify(ZipEntry e, byte[] eData)145         void verify(ZipEntry e, byte[] eData) throws Exception {
146             byte[] data  = (this.data == null) ? new byte[]{} : this.data;
147             byte[] extra = (this.extra != null && this.extra.length == 0) ?
148                 null : this.extra;
149             check(name.equals(e.getName()));
150             check(method == e.getMethod());
151             check((((comment == null) || comment.equals(""))
152                    && (e.getComment() == null))
153                   || comment.equals(e.getComment()));
154             check(equalsExtraData(extra, e.getExtra()));
155             check(Arrays.equals(data, eData));
156             check(e.getSize() == data.length);
157             check((method == ZipEntry.DEFLATED) ||
158                   (e.getCompressedSize() == data.length));
159         }
160 
verify(ZipFile f)161         void verify(ZipFile f) throws Exception {
162             ZipEntry e = f.getEntry(name);
163             verify(e, getData(f, e));
164         }
165 
verifyZipInputStream(ZipInputStream s)166         void verifyZipInputStream(ZipInputStream s) throws Exception {
167             ZipEntry e = s.getNextEntry();
168 
169             byte[] data = (this.data == null) ? new byte[]{} : this.data;
170             byte[] otherData = new byte[data.length];
171             s.read(otherData);
172             check(Arrays.equals(data, otherData));
173 
174             byte[] extra = (this.extra != null && this.extra.length == 0) ?
175                 null : this.extra;
176             check(equalsExtraData(extra, e.getExtra()));
177             check(name.equals(e.getName()));
178             check(method == e.getMethod());
179             check(e.getSize() == -1 || e.getSize() == data.length);
180             check((method == ZipEntry.DEFLATED) ||
181                   (e.getCompressedSize() == data.length));
182         }
183 
verifyJarInputStream(JarInputStream s)184         void verifyJarInputStream(JarInputStream s) throws Exception {
185             // JarInputStream "automatically" reads the manifest
186             if (name.equals("meta-iNf/ManIfEst.Mf"))
187                 return;
188 
189             verifyZipInputStream(s);
190         }
191     }
192 
193     private static int uniquifier = 86;
uniquify(String name)194     private static String uniquify(String name) {
195         return name + (uniquifier++);
196     }
197 
toBytes(String s)198     private static byte[] toBytes(String s) throws Exception {
199         return s.getBytes("UTF-8");
200     }
201 
toExtra(byte[] bytes)202     private static byte[] toExtra(byte[] bytes) throws Exception {
203         if (bytes == null) return null;
204         // Construct a fake extra field with valid header length
205         byte[] v = new byte[bytes.length + 4];
206         v[0] = (byte) 0x47;
207         v[1] = (byte) 0xff;
208         v[2] = (byte) bytes.length;
209         v[3] = (byte) (bytes.length << 8);
210         System.arraycopy(bytes, 0, v, 4, bytes.length);
211         return v;
212     }
213 
214     private static Random random = new Random();
215 
makeName(int length)216     private static String makeName(int length) {
217         StringBuilder sb = new StringBuilder(length);
218         for (int i = 0; i < length; i++)
219             sb.append((char)(random.nextInt(10000)+1));
220         return sb.toString();
221     }
222 
main(String[] args)223     public static void main(String[] args) throws Exception {
224         File zipName = new File("x.zip");
225         int[]    methods  = {ZipEntry.STORED, ZipEntry.DEFLATED};
226         String[] names    = {makeName(1), makeName(160), makeName(9000)};
227         byte[][] datas    = {null, new byte[]{}, new byte[]{'d'}};
228         byte[][] extras   = {null, new byte[]{}, new byte[]{'e'}};
229         String[] comments = {null, "", "c"};
230 
231         List<Entry> entries = new ArrayList<Entry>();
232 
233         // Highly unusual manifest
234         entries.add(new Entry("meta-iNf/ManIfEst.Mf",
235                               ZipEntry.STORED,
236                               toBytes("maNiFest-VeRsIon: 1.0\n"),
237                               toExtra(toBytes("Can manifests have extra??")),
238                               "Can manifests have comments??"));
239 
240         // The emptiest possible entry
241         entries.add(new Entry("", ZipEntry.STORED, null, null, ""));
242 
243         for (String name : names)
244             for (int method : methods)
245                 for (byte[] data : datas) // datae??
246                     for (byte[] extra : extras)
247                         for (String comment : comments)
248                             entries.add(new Entry(uniquify(name), method, data,
249                                                   toExtra(extra), comment));
250 
251         //----------------------------------------------------------------
252         // Write zip file using ZipOutputStream
253         //----------------------------------------------------------------
254         try (FileOutputStream fos = new FileOutputStream(zipName);
255              ZipOutputStream zos = new ZipOutputStream(fos))
256         {
257             for (Entry e : entries)
258                 e.write(zos);
259         }
260 
261         //----------------------------------------------------------------
262         // Verify zip file contents using ZipFile.getEntry()
263         //----------------------------------------------------------------
264         try (ZipFile f = new ZipFile(zipName)) {
265             for (Entry e : entries)
266                 e.verify(f);
267         }
268 
269         //----------------------------------------------------------------
270         // Verify zip file contents using JarFile.getEntry()
271         //----------------------------------------------------------------
272         try (JarFile f = new JarFile(zipName)) {
273             check(f.getManifest() != null);
274             for (Entry e : entries)
275                 e.verify(f);
276         }
277 
278         //----------------------------------------------------------------
279         // Verify zip file contents using ZipFile.entries()
280         //----------------------------------------------------------------
281         try (ZipFile f = new ZipFile(zipName)) {
282             Enumeration<? extends ZipEntry> en = f.entries();
283             for (Entry e : entries)
284                 e.verify(f, en.nextElement());
285 
286             check(!en.hasMoreElements());
287         }
288 
289         //----------------------------------------------------------------
290         // Verify zip file contents using JarFile.entries()
291         //----------------------------------------------------------------
292         try (JarFile f = new JarFile(zipName)) {
293             Enumeration<? extends ZipEntry> en = f.entries();
294             for (Entry e : entries)
295                 e.verify(f, en.nextElement());
296 
297             check(!en.hasMoreElements());
298         }
299 
300         //----------------------------------------------------------------
301         // Verify zip file contents using ZipInputStream class
302         //----------------------------------------------------------------
303         try (FileInputStream fis = new FileInputStream(zipName);
304              ZipInputStream s = new ZipInputStream(fis)) {
305 
306             for (Entry e : entries)
307                 e.verifyZipInputStream(s);
308         }
309 
310         //----------------------------------------------------------------
311         // Verify zip file contents using JarInputStream class
312         //----------------------------------------------------------------
313         try (FileInputStream fis = new FileInputStream(zipName);
314              JarInputStream s = new JarInputStream(fis)) {
315 
316             // JarInputStream "automatically" reads the manifest
317             check(s.getManifest() != null);
318 
319             for (Entry e : entries)
320                 e.verifyJarInputStream(s);
321         }
322 
323 //      String cmd = "unzip -t " + zipName.getPath() + " >/dev/tty";
324 //      new ProcessBuilder(new String[]{"/bin/sh", "-c", cmd}).start().waitFor();
325 
326         zipName.deleteOnExit();
327 
328         System.out.printf("passed = %d, failed = %d%n", passed, failed);
329         if (failed > 0) throw new Exception("Some tests failed");
330     }
331 }
332