1 /*
   2  * Copyright (c) 2020, 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 package util;
  25 
  26 import org.testng.annotations.DataProvider;
  27 
  28 import java.io.File;
  29 import java.io.IOException;
  30 import java.io.InputStream;
  31 import java.nio.charset.StandardCharsets;
  32 import java.nio.file.FileSystem;
  33 import java.nio.file.FileSystems;
  34 import java.nio.file.Files;
  35 import java.nio.file.Path;
  36 import java.security.SecureRandom;
  37 import java.util.Arrays;
  38 import java.util.Comparator;
  39 import java.util.Map;
  40 import java.util.stream.Stream;
  41 import java.util.zip.ZipEntry;
  42 import java.util.zip.ZipFile;
  43 
  44 import static java.lang.String.format;
  45 import static java.util.stream.Collectors.joining;
  46 import static org.testng.Assert.*;
  47 
  48 public class ZipFsBaseTest {
  49 
  50     protected static final Path HERE = Path.of(".");
  51     // Enable for permissions output
  52     protected static final boolean DEBUG = false;
  53     private static final SecureRandom random = new SecureRandom();
  54 
  55     /**
  56      * DataProvider used to specify the Zip FS properties to use when creating
  57      * the Zip File along with the compression method used
  58      *
  59      * @return Zip FS properties and compression method used by the tests
  60      */
  61     @DataProvider(name = "zipfsMap")
  62     protected Object[][] zipfsMap() {
  63         return new Object[][]{
  64                 {Map.of("create", "true"), ZipEntry.DEFLATED},
  65                 {Map.of("create", "true", "noCompression", "true"),
  66                         ZipEntry.STORED},
  67                 {Map.of("create", "true", "noCompression", "false"),
  68                         ZipEntry.DEFLATED}
  69         };
  70     }
  71 
  72     /**
  73      * DataProvider with the compression methods to be used for a given test run
  74      *
  75      * @return Compression methods to test with
  76      */
  77     @DataProvider(name = "compressionMethods")
  78     protected Object[][] compressionMethods() {
  79         return new Object[][]{
  80                 {ZipEntry.DEFLATED},
  81                 {ZipEntry.STORED}
  82         };
  83     }
  84 
  85     /**
  86      * Utility method to return a formatted String of the key:value entries for
  87      * a Map
  88      *
  89      * @param env Map to format
  90      * @return Formatted string of the Map entries
  91      */
  92     private static String formatMap(Map<String, ?> env) {
  93         return env.entrySet().stream()
  94                 .map(e -> format("(%s:%s)", e.getKey(), e.getValue()))
  95                 .collect(joining(", "));
  96     }
  97 
  98     /**
  99      * Generate a temporary file Path
 100      *
 101      * @param dir    Directory used to create the path
 102      * @param prefix The prefix string used to create the path
 103      * @param suffix The suffix string used to create the path
 104      * @return Path that was generated
 105      */
 106     protected static Path generatePath(Path dir, String prefix, String suffix) {
 107         long n = random.nextLong();
 108         String s = prefix + Long.toUnsignedString(n) + suffix;
 109         Path name = dir.getFileSystem().getPath(s);
 110         // the generated name should be a simple file name
 111         if (name.getParent() != null)
 112             throw new IllegalArgumentException("Invalid prefix or suffix");
 113         return dir.resolve(name);
 114     }
 115 
 116     /**
 117      * Verify that the given path is a Zip file containing exactly the
 118      * given entries.
 119      */
 120     protected static void verify(Path zipfile, Entry... entries) throws IOException {
 121         // check entries with Zip API
 122         try (ZipFile zf = new ZipFile(zipfile.toFile())) {
 123             // check entry count
 124             assertEquals(entries.length, zf.size());
 125 
 126             // Check compression method and content of each entry
 127             for (Entry e : entries) {
 128                 ZipEntry ze = zf.getEntry(e.name);
 129                 assertNotNull(ze);
 130                 if (DEBUG) {
 131                     System.out.printf("Entry Name: %s, method: %s, Expected Method: %s%n",
 132                             e.name, ze.getMethod(), e.method);
 133                 }
 134                 assertEquals(e.method, ze.getMethod());
 135                 try (InputStream in = zf.getInputStream(ze)) {
 136                     byte[] bytes = in.readAllBytes();
 137                     if (DEBUG) {
 138                         System.out.printf("bytes= %s, actual=%s%n",
 139                                 new String(bytes), new String(e.bytes));
 140                     }
 141 
 142                     assertTrue(Arrays.equals(bytes, e.bytes));
 143                 }
 144             }
 145         }
 146 
 147         // Check entries with FileSystem API
 148         try (FileSystem fs = FileSystems.newFileSystem(zipfile)) {
 149             // cCheck entry count
 150             Path top = fs.getPath("/");
 151             long count = Files.find(top, Integer.MAX_VALUE,
 152                     (path, attrs) -> attrs.isRegularFile()).count();
 153             assertEquals(entries.length, count);
 154 
 155             // Check content of each entry
 156             for (Entry e : entries) {
 157                 Path file = fs.getPath(e.name);
 158                 if (DEBUG) {
 159                     System.out.printf("Entry name = %s, bytes= %s, actual=%s%n", e.name,
 160                             new String(Files.readAllBytes(file)), new String(e.bytes));
 161                 }
 162                 assertEquals(Files.readAllBytes(file), e.bytes);
 163             }
 164         }
 165     }
 166 
 167     /**
 168      * Create a Zip File System using the specified properties and a Zip file
 169      * with the specified number of entries
 170      *
 171      * @param zipFile Path to the Zip File to create/update
 172      * @param env     Properties used for creating the Zip Filesystem
 173      * @param source  The path of the file to add to the Zip File
 174      * @throws IOException If an error occurs while creating/updating the Zip file
 175      */
 176     protected void zip(Path zipFile, Map<String, String> env, Path source) throws IOException {
 177         if (DEBUG) {
 178             System.out.printf("File:%s, adding:%s%n", zipFile.toAbsolutePath(), source);
 179         }
 180         try (FileSystem zipfs =
 181                      FileSystems.newFileSystem(zipFile, env)) {
 182             Files.copy(source, zipfs.getPath(source.getFileName().toString()));
 183         }
 184     }
 185 
 186     /**
 187      * Create a Zip File System using the specified properties and a Zip file
 188      * with the specified number of entries
 189      *
 190      * @param zipFile Path to the Zip File to create
 191      * @param env     Properties used for creating the Zip Filesystem
 192      * @param entries The entries to add to the Zip File
 193      * @throws IOException If an error occurs while creating the Zip file
 194      */
 195     protected void zip(Path zipFile, Map<String, ?> env,
 196                     Entry... entries) throws IOException {
 197         if (DEBUG) {
 198             System.out.printf("Creating file: %s, env: %s%n", zipFile, formatMap(env));
 199         }
 200         try (FileSystem zipfs = FileSystems.newFileSystem(zipFile, env)) {
 201             for (Entry e : entries) {
 202                 Path path = zipfs.getPath(e.name);
 203                 if (path.getParent() != null) {
 204                     Files.createDirectories(path.getParent());
 205                 }
 206                 Files.write(path, e.bytes);
 207             }
 208         }
 209     }
 210 
 211     /**
 212      * Recursively remove a Directory
 213      *
 214      * @param dir Directory to delete
 215      * @throws IOException If an error occurs
 216      */
 217     protected static void rmdir(Path dir) throws IOException {
 218         // Nothing to do if the the file does not exist
 219         if (!Files.exists(dir)) {
 220             return;
 221         }
 222         try (Stream<Path> walk = Files.walk(dir)) {
 223             walk.sorted(Comparator.reverseOrder())
 224                     .map(Path::toFile)
 225                     .peek(System.out::println)
 226                     .forEach(File::delete);
 227         }
 228     }
 229 
 230     /**
 231      * Represents an entry in a Zip file. An entry encapsulates a name, a
 232      * compression method, and its contents/data.
 233      */
 234     public static class Entry {
 235         public final String name;
 236         public final int method;
 237         public final byte[] bytes;
 238 
 239         public Entry(String name, int method, String contents) {
 240             this.name = name;
 241             this.method = method;
 242             this.bytes = contents.getBytes(StandardCharsets.UTF_8);
 243         }
 244 
 245         public static Entry of(String name, int method, String contents) {
 246             return new Entry(name, method, contents);
 247         }
 248 
 249         /**
 250          * Returns a new Entry with the same name and compression method as this
 251          * Entry but with the given content.
 252          */
 253         public Entry content(String contents) {
 254             return new Entry(name, method, contents);
 255         }
 256     }
 257 }