1 /*
2  * Copyright (c) 2019 SAP SE. 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 import java.io.File;
25 import java.io.FileOutputStream;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.nio.file.*;
29 import java.nio.file.attribute.BasicFileAttributes;
30 import java.nio.file.attribute.GroupPrincipal;
31 import java.nio.file.attribute.PosixFileAttributeView;
32 import java.nio.file.attribute.PosixFileAttributes;
33 import java.nio.file.attribute.PosixFilePermission;
34 import java.nio.file.attribute.PosixFilePermissions;
35 import java.nio.file.attribute.UserPrincipal;
36 import java.security.AccessController;
37 import java.security.PrivilegedAction;
38 import java.security.PrivilegedActionException;
39 import java.security.PrivilegedExceptionAction;
40 import java.util.Collections;
41 import java.util.Enumeration;
42 import java.util.HashMap;
43 import java.util.Map;
44 import java.util.Set;
45 import java.util.concurrent.atomic.AtomicInteger;
46 import java.util.jar.JarEntry;
47 import java.util.jar.JarFile;
48 import java.util.spi.ToolProvider;
49 import java.util.zip.ZipEntry;
50 import java.util.zip.ZipFile;
51 
52 import org.testng.annotations.Test;
53 
54 import static java.nio.file.attribute.PosixFilePermission.GROUP_EXECUTE;
55 import static java.nio.file.attribute.PosixFilePermission.GROUP_READ;
56 import static java.nio.file.attribute.PosixFilePermission.GROUP_WRITE;
57 import static java.nio.file.attribute.PosixFilePermission.OTHERS_EXECUTE;
58 import static java.nio.file.attribute.PosixFilePermission.OTHERS_READ;
59 import static java.nio.file.attribute.PosixFilePermission.OTHERS_WRITE;
60 import static java.nio.file.attribute.PosixFilePermission.OWNER_EXECUTE;
61 import static java.nio.file.attribute.PosixFilePermission.OWNER_READ;
62 import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE;
63 import static org.testng.Assert.assertEquals;
64 import static org.testng.Assert.assertNotNull;
65 import static org.testng.Assert.assertNull;
66 import static org.testng.Assert.assertTrue;
67 import static org.testng.Assert.fail;
68 
69 /**
70  * @test
71  * @bug 8213031
72  * @modules jdk.zipfs
73  *          jdk.jartool
74  * @run testng TestPosix
75  * @run testng/othervm/java.security.policy=test.policy.posix TestPosix
76  * @summary Test POSIX zip file operations.
77  */
78 public class TestPosix {
79     private static final ToolProvider JAR_TOOL = ToolProvider.findFirst("jar")
80         .orElseThrow(()->new RuntimeException("jar tool not found"));
81 
82     // files and directories
83     private static final Path ZIP_FILE = Paths.get("testPosix.zip");
84     private static final Path JAR_FILE = Paths.get("testPosix.jar");
85     private static final Path ZIP_FILE_COPY = Paths.get("testPosixCopy.zip");
86     private static final Path UNZIP_DIR = Paths.get("unzip/");
87 
88     // permission sets
89     private static final Set<PosixFilePermission> ALLPERMS =
90         PosixFilePermissions.fromString("rwxrwxrwx");
91     private static final Set<PosixFilePermission> EMPTYPERMS =
92         Collections.<PosixFilePermission>emptySet();
93     private static final Set<PosixFilePermission> UR = Set.of(OWNER_READ);
94     private static final Set<PosixFilePermission> UW = Set.of(OWNER_WRITE);
95     private static final Set<PosixFilePermission> UE = Set.of(OWNER_EXECUTE);
96     private static final Set<PosixFilePermission> GR = Set.of(GROUP_READ);
97     private static final Set<PosixFilePermission> GW = Set.of(GROUP_WRITE);
98     private static final Set<PosixFilePermission> GE = Set.of(GROUP_EXECUTE);
99     private static final Set<PosixFilePermission> OR = Set.of(OTHERS_READ);
100     private static final Set<PosixFilePermission> OW = Set.of(OTHERS_WRITE);
101     private static final Set<PosixFilePermission> OE = Set.of(OTHERS_EXECUTE);
102 
103     // principals
104     private static final UserPrincipal DUMMY_USER = ()->"defusr";
105     private static final GroupPrincipal DUMMY_GROUP = ()->"defgrp";
106 
107     // FS open options
108     private static final Map<String, Object> ENV_DEFAULT = Collections.<String, Object>emptyMap();
109     private static final Map<String, Object> ENV_POSIX = Map.of("enablePosixFileAttributes", true);
110 
111     // misc
112     private static final CopyOption[] COPY_ATTRIBUTES = {StandardCopyOption.COPY_ATTRIBUTES};
113     private static final Map<String, ZipFileEntryInfo> ENTRIES = new HashMap<>();
114 
115     private int entriesCreated;
116 
117     static enum checkExpects {
118         contentOnly,
119         noPermDataInZip,
120         permsInZip,
121         permsPosix
122     }
123 
124     static class ZipFileEntryInfo {
125         // permissions to set initially
126         private final Set<PosixFilePermission> intialPerms;
127         // permissions to set in a later call
128         private final Set<PosixFilePermission> laterPerms;
129         // permissions that should be effective in the zip file
130         private final Set<PosixFilePermission> permsInZip;
131         // permissions that should be returned by zipfs w/Posix support
132         private final Set<PosixFilePermission> permsPosix;
133         // entry is a directory
134         private final boolean isDir;
135         // need additional read flag in copy test
136         private final boolean setReadFlag;
137 
ZipFileEntryInfo(Set<PosixFilePermission> initialPerms, Set<PosixFilePermission> laterPerms, Set<PosixFilePermission> permsInZip, Set<PosixFilePermission> permsZipPosix, boolean isDir, boolean setReadFlag)138         private ZipFileEntryInfo(Set<PosixFilePermission> initialPerms, Set<PosixFilePermission> laterPerms,
139             Set<PosixFilePermission> permsInZip, Set<PosixFilePermission> permsZipPosix, boolean isDir, boolean setReadFlag)
140         {
141             this.intialPerms = initialPerms;
142             this.laterPerms = laterPerms;
143             this.permsInZip = permsInZip;
144             this.permsPosix = permsZipPosix;
145             this.isDir = isDir;
146             this.setReadFlag = setReadFlag;
147         }
148     }
149 
150     static class CopyVisitor extends SimpleFileVisitor<Path> {
151         private Path from, to;
152         private boolean copyPerms;
153 
CopyVisitor(Path from, Path to)154         CopyVisitor(Path from, Path to) {
155             this.from = from;
156             this.to = to;
157         }
158 
CopyVisitor(Path from, Path to, boolean copyPerms)159         CopyVisitor(Path from, Path to, boolean copyPerms) {
160             this.from = from;
161             this.to = to;
162             this.copyPerms = copyPerms;
163         }
164 
165         @Override
preVisitDirectory(Path dir, BasicFileAttributes attrs)166         public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
167             FileVisitResult rc = super.preVisitDirectory(dir, attrs);
168             Path target = to.resolve(from.relativize(dir).toString());
169             if (!Files.exists(target)) {
170                 Files.copy(dir, target, COPY_ATTRIBUTES);
171                 if (copyPerms) {
172                     Files.setPosixFilePermissions(target, Files.getPosixFilePermissions(dir));
173                 }
174             }
175             return rc;
176         }
177 
178         @Override
visitFile(Path file, BasicFileAttributes attrs)179         public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
180             FileVisitResult rc = super.visitFile(file, attrs);
181             Path target = to.resolve(from.relativize(file).toString());
182             Files.copy(file, target, COPY_ATTRIBUTES);
183             if (copyPerms) {
184                 Files.setPosixFilePermissions(target, Files.getPosixFilePermissions(file));
185             }
186             return rc;
187         }
188     }
189 
190     static class DeleteVisitor extends SimpleFileVisitor<Path> {
191         @Override
postVisitDirectory(Path dir, IOException exc)192         public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
193             FileVisitResult rc = super.postVisitDirectory(dir, exc);
194             Files.delete(dir);
195             return rc;
196         }
197 
198         @Override
visitFile(Path file, BasicFileAttributes attrs)199         public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
200             FileVisitResult rc = super.visitFile(file, attrs);
201             Files.delete(file);
202             return rc;
203         }
204     }
205 
206     @FunctionalInterface
207     static interface Executor {
doIt()208         void doIt() throws IOException;
209     }
210 
211     static {
212         ENTRIES.put("dir",        new ZipFileEntryInfo(ALLPERMS,   null, ALLPERMS,   ALLPERMS,   true,  false));
213         ENTRIES.put("uread",      new ZipFileEntryInfo(UR,         null, UR,         UR,         false, false));
214         ENTRIES.put("uwrite",     new ZipFileEntryInfo(UW,         null, UW,         UW,         false, true));
215         ENTRIES.put("uexec",      new ZipFileEntryInfo(UE,         null, UE,         UE,         false, true));
216         ENTRIES.put("gread",      new ZipFileEntryInfo(GR,         null, GR,         GR,         false, true));
217         ENTRIES.put("gwrite",     new ZipFileEntryInfo(GW,         null, GW,         GW,         false, true));
218         ENTRIES.put("gexec",      new ZipFileEntryInfo(GE,         null, GE,         GE,         false, true));
219         ENTRIES.put("oread",      new ZipFileEntryInfo(OR,         null, OR,         OR,         false, true));
220         ENTRIES.put("owrite",     new ZipFileEntryInfo(OW,         null, OW,         OW,         false, true));
221         ENTRIES.put("oexec",      new ZipFileEntryInfo(OE,         null, OE,         OE,         false, true));
222         ENTRIES.put("emptyperms", new ZipFileEntryInfo(EMPTYPERMS, null, EMPTYPERMS, EMPTYPERMS, false, true));
223         ENTRIES.put("noperms",    new ZipFileEntryInfo(null,       null, null,       ALLPERMS,   false, false));
224         ENTRIES.put("permslater", new ZipFileEntryInfo(null,       UR,   UR,         UR,         false, false));
225     }
226 
expectedDefaultOwner(Path zf)227     private static String expectedDefaultOwner(Path zf) {
228         try {
229             try {
230                 PrivilegedExceptionAction<String> pa = ()->Files.getOwner(zf).getName();
231                 return AccessController.doPrivileged(pa);
232             } catch (UnsupportedOperationException e) {
233                 // if we can't get the owner of the file, we fall back to system property user.name
234                 PrivilegedAction<String> pa = ()->System.getProperty("user.name");
235                 return AccessController.doPrivileged(pa);
236             }
237         } catch (PrivilegedActionException | SecurityException e) {
238             System.out.println("Caught " + e.getClass().getName() + "(" + e.getMessage() +
239                 ") when running a privileged operation to get the default owner.");
240             return null;
241         }
242     }
243 
expectedDefaultGroup(Path zf, String defaultOwner)244     private static String expectedDefaultGroup(Path zf, String defaultOwner) {
245         try {
246             try {
247                 PosixFileAttributeView zfpv = Files.getFileAttributeView(zf, PosixFileAttributeView.class);
248                 if (zfpv == null) {
249                     return defaultOwner;
250                 }
251                 PrivilegedExceptionAction<String> pa = ()->zfpv.readAttributes().group().getName();
252                 return AccessController.doPrivileged(pa);
253             } catch (UnsupportedOperationException e) {
254                 return defaultOwner;
255             }
256         } catch (PrivilegedActionException | SecurityException e) {
257             System.out.println("Caught an exception when running a privileged operation to get the default group.");
258             e.printStackTrace();
259             return null;
260         }
261     }
262 
putEntry(FileSystem fs, String name, ZipFileEntryInfo entry)263     private void putEntry(FileSystem fs, String name, ZipFileEntryInfo entry) throws IOException {
264         if (entry.isDir) {
265             if (entry.intialPerms == null) {
266                 Files.createDirectory(fs.getPath(name));
267             } else {
268                 Files.createDirectory(fs.getPath(name), PosixFilePermissions.asFileAttribute(entry.intialPerms));
269             }
270 
271         } else {
272             if (entry.intialPerms == null) {
273                 Files.createFile(fs.getPath(name));
274             } else {
275                 Files.createFile(fs.getPath(name), PosixFilePermissions.asFileAttribute(entry.intialPerms));
276             }
277         }
278         if (entry.laterPerms != null) {
279             Files.setAttribute(fs.getPath(name), "zip:permissions", entry.laterPerms);
280         }
281         entriesCreated++;
282     }
283 
createTestZipFile(Path zpath, Map<String, Object> env)284     private FileSystem createTestZipFile(Path zpath, Map<String, Object> env) throws IOException {
285         if (Files.exists(zpath)) {
286             System.out.println("Deleting old " + zpath + "...");
287             Files.delete(zpath);
288         }
289         System.out.println("Creating " + zpath + "...");
290         entriesCreated = 0;
291         var opts = new HashMap<String, Object>();
292         opts.putAll(env);
293         opts.put("create", true);
294         FileSystem fs = FileSystems.newFileSystem(zpath, opts);
295         for (String name : ENTRIES.keySet()) {
296             putEntry(fs, name, ENTRIES.get(name));
297         }
298         return fs;
299     }
300 
createEmptyZipFile(Path zpath, Map<String, Object> env)301     private FileSystem createEmptyZipFile(Path zpath, Map<String, Object> env) throws IOException {
302         if (Files.exists(zpath)) {
303             System.out.println("Deleting old " + zpath + "...");
304             Files.delete(zpath);
305         }
306         System.out.println("Creating " + zpath + "...");
307         var opts = new HashMap<String, Object>();
308         opts.putAll(env);
309         opts.put("create", true);
310         return FileSystems.newFileSystem(zpath, opts);
311     }
312 
delTree(Path p)313     private void delTree(Path p) throws IOException {
314         if (Files.exists(p)) {
315             Files.walkFileTree(p, new DeleteVisitor());
316         }
317     }
318 
addOwnerRead(Path root)319     private void addOwnerRead(Path root) throws IOException {
320         for (String name : ENTRIES.keySet()) {
321             ZipFileEntryInfo ei = ENTRIES.get(name);
322             if (!ei.setReadFlag) {
323                 continue;
324             }
325             Path setReadOn = root.resolve(name);
326             Set<PosixFilePermission> perms = Files.getPosixFilePermissions(setReadOn);
327             perms.add(OWNER_READ);
328             Files.setPosixFilePermissions(setReadOn, perms);
329         }
330     }
331 
removeOwnerRead(Path root)332     private void removeOwnerRead(Path root) throws IOException {
333         for (String name : ENTRIES.keySet()) {
334             ZipFileEntryInfo ei = ENTRIES.get(name);
335             if (!ei.setReadFlag) {
336                 continue;
337             }
338             Path removeReadFrom = root.resolve(name);
339             Set<PosixFilePermission> perms = Files.getPosixFilePermissions(removeReadFrom);
340             perms.remove(OWNER_READ);
341             Files.setPosixFilePermissions(removeReadFrom, perms);
342         }
343     }
344 
345     @SuppressWarnings("unchecked")
checkEntry(Path file, checkExpects expected)346     private void checkEntry(Path file, checkExpects expected) {
347         System.out.println("Checking " + file + "...");
348         String name = file.getFileName().toString();
349         ZipFileEntryInfo ei = ENTRIES.get(name);
350         assertNotNull(ei, "Found unknown entry " + name + ".");
351         BasicFileAttributes attrs = null;
352         if (expected == checkExpects.permsPosix) {
353             try {
354                 attrs = Files.readAttributes(file, PosixFileAttributes.class);
355             } catch (IOException e) {
356                 e.printStackTrace();
357                 fail("Caught IOException reading file attributes (posix) for " + name + ": " + e.getMessage());
358             }
359         } else {
360             try {
361                 attrs = Files.readAttributes(file, BasicFileAttributes.class);
362             } catch (IOException e) {
363                 e.printStackTrace();
364                 fail("Caught IOException reading file attributes (basic) " + name + ": " + e.getMessage());
365             }
366         }
367         assertEquals(Files.isDirectory(file), ei.isDir, "Unexpected directory attribute for:" + System.lineSeparator() + attrs);
368 
369         if (expected == checkExpects.contentOnly) {
370             return;
371         }
372 
373         Set<PosixFilePermission> permissions;
374         if (expected == checkExpects.permsPosix) {
375             try {
376                 permissions = Files.getPosixFilePermissions(file);
377             } catch (IOException e) {
378                 e.printStackTrace();
379                 fail("Caught IOException getting permission attribute for:" + System.lineSeparator() + attrs);
380                 return;
381             }
382             comparePermissions(ei.permsPosix, permissions);
383         } else if (expected == checkExpects.permsInZip || expected == checkExpects.noPermDataInZip) {
384             try {
385                 permissions = (Set<PosixFilePermission>)Files.getAttribute(file, "zip:permissions");
386             } catch (IOException e) {
387                 e.printStackTrace();
388                 fail("Caught IOException getting permission attribute for:" + System.lineSeparator() + attrs);
389                 return;
390             }
391             comparePermissions(expected == checkExpects.noPermDataInZip ? null : ei.permsInZip, permissions);
392         }
393     }
394 
doCheckEntries(Path path, checkExpects expected)395     private void doCheckEntries(Path path, checkExpects expected) throws IOException {
396         AtomicInteger entries = new AtomicInteger();
397 
398         try (DirectoryStream<Path> paths = Files.newDirectoryStream(path)) {
399             paths.forEach(file -> {
400                 entries.getAndIncrement();
401                 checkEntry(file, expected);
402             });
403         }
404         System.out.println("Number of entries: " + entries.get() + ".");
405         assertEquals(entries.get(), entriesCreated, "File contained wrong number of entries.");
406     }
407 
checkEntries(FileSystem fs, checkExpects expected)408     private void checkEntries(FileSystem fs, checkExpects expected) throws IOException {
409         System.out.println("Checking permissions on file system " + fs + "...");
410         doCheckEntries(fs.getPath("/"), expected);
411     }
412 
checkEntries(Path path, checkExpects expected)413     private void checkEntries(Path path, checkExpects expected) throws IOException {
414         System.out.println("Checking permissions on path " + path + "...");
415         doCheckEntries(path, expected);
416     }
417 
throwsUOE(Executor e)418     private boolean throwsUOE(Executor e) throws IOException {
419         try {
420             e.doIt();
421             return false;
422         } catch (UnsupportedOperationException exc) {
423             return true;
424         }
425     }
426 
comparePermissions(Set<PosixFilePermission> expected, Set<PosixFilePermission> actual)427     private void comparePermissions(Set<PosixFilePermission> expected, Set<PosixFilePermission> actual) {
428         if (expected == null) {
429             assertNull(actual, "Permissions are not null");
430         } else {
431             assertNotNull(actual, "Permissions are null.");
432             assertEquals(actual.size(), expected.size(), "Unexpected number of permissions (" +
433                 actual.size() + " received vs " + expected.size() + " expected).");
434             for (PosixFilePermission p : expected) {
435                 assertTrue(actual.contains(p), "Posix permission " + p + " missing.");
436             }
437         }
438     }
439 
440     /**
441      * This tests whether the entries in a zip file created w/o
442      * Posix support are correct.
443      *
444      * @throws IOException
445      */
446     @Test
testDefault()447     public void testDefault() throws IOException {
448         // create zip file using zipfs with default options
449         createTestZipFile(ZIP_FILE, ENV_DEFAULT).close();
450         // check entries on zipfs with default options
451         try (FileSystem zip = FileSystems.newFileSystem(ZIP_FILE, ENV_DEFAULT)) {
452             checkEntries(zip, checkExpects.permsInZip);
453         }
454         // check entries on zipfs with posix options
455         try (FileSystem zip = FileSystems.newFileSystem(ZIP_FILE, ENV_POSIX)) {
456             checkEntries(zip, checkExpects.permsPosix);
457         }
458     }
459 
460     /**
461      * This tests whether the entries in a zip file created w/
462      * Posix support are correct.
463      *
464      * @throws IOException
465      */
466     @Test
testPosix()467     public void testPosix() throws IOException {
468         // create zip file using zipfs with posix option
469         createTestZipFile(ZIP_FILE, ENV_POSIX).close();
470         // check entries on zipfs with default options
471         try (FileSystem zip = FileSystems.newFileSystem(ZIP_FILE, ENV_DEFAULT)) {
472             checkEntries(zip, checkExpects.permsInZip);
473         }
474         // check entries on zipfs with posix options
475         try (FileSystem zip = FileSystems.newFileSystem(ZIP_FILE, ENV_POSIX)) {
476             checkEntries(zip, checkExpects.permsPosix);
477         }
478     }
479 
480     /**
481      * This tests whether the entries in a zip file copied from another
482      * are correct.
483      *
484      * @throws IOException
485      */
486     @Test
testCopy()487     public void testCopy() throws IOException {
488         // copy zip to zip with default options
489         try (FileSystem zipIn = createTestZipFile(ZIP_FILE, ENV_DEFAULT);
490              FileSystem zipOut = createEmptyZipFile(ZIP_FILE_COPY, ENV_DEFAULT)) {
491             Path from = zipIn.getPath("/");
492             Files.walkFileTree(from, new CopyVisitor(from, zipOut.getPath("/")));
493         }
494         // check entries on copied zipfs with default options
495         try (FileSystem zip = FileSystems.newFileSystem(ZIP_FILE_COPY, ENV_DEFAULT)) {
496             checkEntries(zip, checkExpects.permsInZip);
497         }
498         // check entries on copied zipfs with posix options
499         try (FileSystem zip = FileSystems.newFileSystem(ZIP_FILE_COPY, ENV_POSIX)) {
500             checkEntries(zip, checkExpects.permsPosix);
501         }
502     }
503 
504     /**
505      * This tests whether the entries of a zip file look correct after extraction
506      * and re-packing. When not using zipfs with Posix support, we expect the
507      * effective permissions in the resulting zip file to be empty.
508      *
509      * @throws IOException
510      */
511     @Test
testUnzipDefault()512     public void testUnzipDefault() throws IOException {
513         delTree(UNZIP_DIR);
514         Files.createDirectory(UNZIP_DIR);
515 
516         try (FileSystem srcZip = createTestZipFile(ZIP_FILE, ENV_DEFAULT)) {
517             Path from = srcZip.getPath("/");
518             Files.walkFileTree(from, new CopyVisitor(from, UNZIP_DIR));
519         }
520 
521         // we just check that the entries got extracted to file system
522         checkEntries(UNZIP_DIR, checkExpects.contentOnly);
523 
524         // the target zip file is opened with Posix support
525         // but we expect no permission data to be copied using the default copy method
526         try (FileSystem tgtZip = createEmptyZipFile(ZIP_FILE_COPY, ENV_POSIX)) {
527             Files.walkFileTree(UNZIP_DIR, new CopyVisitor(UNZIP_DIR, tgtZip.getPath("/")));
528         }
529 
530         // check entries on copied zipfs - no permission data should exist
531         try (FileSystem zip = FileSystems.newFileSystem(ZIP_FILE_COPY, ENV_DEFAULT)) {
532             checkEntries(zip, checkExpects.noPermDataInZip);
533         }
534     }
535 
536     /**
537      * This tests whether the entries of a zip file look correct after extraction
538      * and re-packing. If the default file system supports Posix, we test whether we
539      * correctly carry the Posix permissions. Otherwise there's not much to test in
540      * this method.
541      *
542      * @throws IOException
543      */
544     @Test
testUnzipPosix()545     public void testUnzipPosix() throws IOException {
546         delTree(UNZIP_DIR);
547         Files.createDirectory(UNZIP_DIR);
548 
549         try {
550             Files.getPosixFilePermissions(UNZIP_DIR);
551         } catch (Exception e) {
552             // if we run into any exception here, be it because of the fact that the file system
553             // is not Posix or if we have insufficient security permissions, we can't do this test.
554             System.out.println("This can't be tested here because of " + e);
555             return;
556         }
557 
558         try (FileSystem srcZip = createTestZipFile(ZIP_FILE, ENV_POSIX)) {
559             Path from = srcZip.getPath("/");
560             // copy permissions as well
561             Files.walkFileTree(from, new CopyVisitor(from, UNZIP_DIR, true));
562         }
563 
564         // permissions should have been propagated to file system
565         checkEntries(UNZIP_DIR, checkExpects.permsPosix);
566 
567         try (FileSystem tgtZip = createEmptyZipFile(ZIP_FILE_COPY, ENV_POSIX)) {
568             // Make some files owner readable to be able to copy them into the zipfs
569             addOwnerRead(UNZIP_DIR);
570 
571             // copy permissions as well
572             Files.walkFileTree(UNZIP_DIR, new CopyVisitor(UNZIP_DIR, tgtZip.getPath("/"), true));
573 
574             // Fix back all the files in the target zip file which have been made readable before
575             removeOwnerRead(tgtZip.getPath("/"));
576         }
577 
578         // check entries on copied zipfs - permission data should have been propagated
579         try (FileSystem zip = FileSystems.newFileSystem(ZIP_FILE_COPY, ENV_POSIX)) {
580             checkEntries(zip, checkExpects.permsPosix);
581         }
582     }
583 
584     /**
585      * Tests POSIX default behavior.
586      *
587      * @throws IOException
588      */
589     @Test
testPosixDefaults()590     public void testPosixDefaults() throws IOException {
591         // test with posix = false, expect UnsupportedOperationException
592         try (FileSystem zipIn = createTestZipFile(ZIP_FILE, ENV_DEFAULT)) {
593             var entry = zipIn.getPath("/dir");
594             assertTrue(throwsUOE(()->Files.getPosixFilePermissions(entry)));
595             assertTrue(throwsUOE(()->Files.setPosixFilePermissions(entry, UW)));
596             assertTrue(throwsUOE(()->Files.getOwner(entry)));
597             assertTrue(throwsUOE(()->Files.setOwner(entry, DUMMY_USER)));
598             assertTrue(throwsUOE(()->Files.getFileAttributeView(entry, PosixFileAttributeView.class)));
599         }
600 
601         // test with posix = true -> default values
602         try (FileSystem zipIn = FileSystems.newFileSystem(ZIP_FILE, ENV_POSIX)) {
603             String defaultOwner = expectedDefaultOwner(ZIP_FILE);
604             String defaultGroup = expectedDefaultGroup(ZIP_FILE, defaultOwner);
605             var entry = zipIn.getPath("/noperms");
606             comparePermissions(ALLPERMS, Files.getPosixFilePermissions(entry));
607             var owner = Files.getOwner(entry);
608             assertNotNull(owner, "owner should not be null");
609             if (defaultOwner != null) {
610                 assertEquals(owner.getName(), defaultOwner);
611             }
612             Files.setOwner(entry, DUMMY_USER);
613             assertEquals(Files.getOwner(entry), DUMMY_USER);
614             var view = Files.getFileAttributeView(entry, PosixFileAttributeView.class);
615             var group = view.readAttributes().group();
616             assertNotNull(group, "group must not be null");
617             if (defaultGroup != null) {
618                 assertEquals(group.getName(), defaultGroup);
619             }
620             view.setGroup(DUMMY_GROUP);
621             assertEquals(view.readAttributes().group(), DUMMY_GROUP);
622             entry = zipIn.getPath("/uexec");
623             Files.setPosixFilePermissions(entry, GR); // will be persisted
624             comparePermissions(GR, Files.getPosixFilePermissions(entry));
625         }
626 
627         // test with posix = true + custom defaults of type String
628         try (FileSystem zipIn = FileSystems.newFileSystem(ZIP_FILE, Map.of("enablePosixFileAttributes", true,
629             "defaultOwner", "auser", "defaultGroup", "agroup", "defaultPermissions", "r--------")))
630         {
631             var entry = zipIn.getPath("/noperms");
632             comparePermissions(UR, Files.getPosixFilePermissions(entry));
633             assertEquals(Files.getOwner(entry).getName(), "auser");
634             var view = Files.getFileAttributeView(entry, PosixFileAttributeView.class);
635             assertEquals(view.readAttributes().group().getName(), "agroup");
636             // check if the change to permissions of /uexec was persisted
637             comparePermissions(GR, Files.getPosixFilePermissions(zipIn.getPath("/uexec")));
638         }
639 
640         // test with posix = true + custom defaults as Objects
641         try (FileSystem zipIn = FileSystems.newFileSystem(ZIP_FILE, Map.of("enablePosixFileAttributes", true,
642             "defaultOwner", DUMMY_USER, "defaultGroup", DUMMY_GROUP, "defaultPermissions", UR)))
643         {
644             var entry = zipIn.getPath("/noperms");
645             comparePermissions(UR, Files.getPosixFilePermissions(entry));
646             assertEquals(Files.getOwner(entry), DUMMY_USER);
647             var view = Files.getFileAttributeView(entry, PosixFileAttributeView.class);
648             assertEquals(view.readAttributes().group(), DUMMY_GROUP);
649         }
650     }
651 
652     /**
653      * Sanity check to test whether the zip file can be unzipped with the java.util.zip API.
654      *
655      * @throws IOException
656      */
657     @Test
testUnzipWithJavaUtilZip()658     public void testUnzipWithJavaUtilZip() throws IOException {
659         createTestZipFile(ZIP_FILE, ENV_DEFAULT).close();
660         delTree(UNZIP_DIR);
661         Files.createDirectory(UNZIP_DIR);
662         File targetDir = UNZIP_DIR.toFile();
663         try (ZipFile zf = new ZipFile(ZIP_FILE.toFile())) {
664             Enumeration<? extends ZipEntry> zenum = zf.entries();
665             while (zenum.hasMoreElements()) {
666                 ZipEntry ze = zenum.nextElement();
667                 File target = new File(targetDir + File.separator + ze.getName());
668                 if (ze.isDirectory()) {
669                     target.mkdir();
670                     continue;
671                 }
672                 try (InputStream is = zf.getInputStream(ze);
673                      FileOutputStream fos = new FileOutputStream(target))
674                 {
675                     while (is.available() > 0) {
676                         fos.write(is.read());
677                     }
678                 }
679             }
680         }
681     }
682 
683     /**
684      * Sanity check to test whether a jar file created with zipfs can be
685      * extracted with the java.util.jar API.
686      *
687      * @throws IOException
688      */
689     @Test
testJarFile()690     public void testJarFile() throws IOException {
691         // create jar file using zipfs with default options
692         createTestZipFile(JAR_FILE, ENV_DEFAULT).close();
693 
694         // extract it using java.util.jar.JarFile
695         delTree(UNZIP_DIR);
696         Files.createDirectory(UNZIP_DIR);
697         File targetDir = UNZIP_DIR.toFile();
698         try (JarFile jf = new JarFile(ZIP_FILE.toFile())) {
699             Enumeration<? extends JarEntry> zenum = jf.entries();
700             while (zenum.hasMoreElements()) {
701                 JarEntry ze = zenum.nextElement();
702                 File target = new File(targetDir + File.separator + ze.getName());
703                 if (ze.isDirectory()) {
704                     target.mkdir();
705                     continue;
706                 }
707                 try (InputStream is = jf.getInputStream(ze);
708                      FileOutputStream fos = new FileOutputStream(target))
709                 {
710                     while (is.available() > 0) {
711                         fos.write(is.read());
712                     }
713                 }
714             }
715         }
716 
717         // extract it using the jar tool
718         delTree(UNZIP_DIR);
719         System.out.println("jar xvf " + JAR_FILE);
720 
721         // the run method catches IOExceptions, we need to expose them
722         int rc = JAR_TOOL.run(System.out, System.err, "xvf", JAR_FILE.toString());
723         assertEquals(rc, 0, "Return code of jar call is " + rc + " but expected 0");
724     }
725 }
726