001/** 002 * Please feel free to use any fragment of the code in this file that you need in your own 003 * work. As far as I am concerned, it's in the public domain. No permission is necessary 004 * or required. Credit is always appreciated if you use a large chunk or base a 005 * significant product on one of my examples, but that's not required either. 006 * 007 * This code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 008 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 009 * PURPOSE. 010 * 011 * --- Joseph A. Huwaldt 012 */ 013package jahuwaldt.io; 014 015import java.io.*; 016import static java.util.Objects.requireNonNull; 017import java.util.zip.ZipEntry; 018import java.util.zip.ZipInputStream; 019import java.util.zip.ZipOutputStream; 020 021/** 022 * This is a utility class of static methods for working with ZIP archive files. 023 * 024 * <p> Modified by: Joseph A. Huwaldt </p> 025 * 026 * @author Behrouz Fallahi, Date: April 28, 2000 027 * @version February 23, 2025 028 */ 029public final class ZipUtils { 030 // This class now requires Java 1.7 or later! 031 032 /** 033 * A list of characters that are illegal on some of the file systems supported by this 034 * program. The characters in this list include: 035 * \n,\r,\t,\0,\f,',?,*,<,>,|,",:,~,@,!,#,[,],=,+,;, and ','. 036 */ 037 public static final char[] ILLEGAL_CHARACTERS 038 = {'\n', '\r', '\t', '\0', '\f', '`', '?', '*', '<', '>', '|', '\"', ':', '~', '@', '!', '#', 039 '[', ']', '=', '+', ';', ','}; 040 041 private static final int BUFFER_SIZE = 2048; 042 043 /** 044 * Prevent instantiation of this utility class. 045 */ 046 private ZipUtils() { 047 } 048 049 /** 050 * Replace any potentially illegal characters from a file name with '_'. 051 * 052 * @param name The file name to be cleaned of potentially illegal characters. May not 053 * be null. 054 * @return The input file name with potentially illegal characters replaced with "_". 055 */ 056 public static String cleanFileName(String name) { 057 if (name.length() == 0) 058 return name; 059 for (char c : ILLEGAL_CHARACTERS) { 060 name = name.replace(c, '_'); 061 } 062 return name; 063 } 064 065 /** 066 * Returns true if the supplied file name contains characters that are illegal on some 067 * file systems. 068 * 069 * @param name The file name to be checked for potentially illegal characters. May not 070 * be null. 071 * @return true if the file name contains potentially illegal characters, false if it 072 * is safe. 073 */ 074 public static boolean hasIllegalChars(String name) { 075 if (name.length() == 0) 076 return false; 077 for (char c : ILLEGAL_CHARACTERS) { 078 if (name.indexOf(c) >= 0) 079 return true; 080 } 081 return false; 082 } 083 084 /** 085 * Write a ZIP archive to the specified output stream made up of all the contents of 086 * the specified file or directory, using the specified compression method. If any of 087 * the files in the specified directory contain characters that are illegal on some 088 * file systems, then those files are skipped and not included in the archive. The 089 * output stream is closed by this method. 090 * 091 * @param fileOrDir The file or directory to be compressed into a ZIP archive. May not 092 * be null. 093 * @param out The output stream to write the ZIP archive data to. May not be 094 * null. 095 * @param zipLevel The compression level (0-9) or java.util.zip.Deflater.DEFAULT_COMPRESSION. 096 * @throws java.io.IOException If there is any problem writing out the ZIP stream. 097 * @see java.util.zip.Deflater 098 */ 099 public static void writeZip(File fileOrDir, OutputStream out, int zipLevel) throws IOException { 100 requireNonNull(fileOrDir, "fileOrDir == null"); 101 requireNonNull(out, "out == null"); 102 103 // Create a ZIP output stream to the specified output file. 104 try (ZipOutputStream zos = new ZipOutputStream(out)) { 105 zos.setLevel(zipLevel); 106 107 if (fileOrDir.isDirectory()) 108 addDir(null, fileOrDir, zos, new byte[BUFFER_SIZE]); 109 else 110 addFile(null, fileOrDir, zos, new byte[BUFFER_SIZE]); 111 } 112 } 113 114 /** 115 * Create a ZIP archive file made up of all the contents of the specified file or 116 * directory, using the specified compression method. If any of the files in the 117 * specified directory contain characters that are illegal on some file systems, then 118 * those files are skipped and not included in the archive. 119 * 120 * @param fileOrDir The file or directory to be compressed into a ZIP archive. May not 121 * be null. 122 * @param zipFile The ZIP file to be written out. May not be null. 123 * @param zipLevel The compression level (0-9) or 124 * java.util.zip.ZipOutputStream.DEFAULT_COMPRESSION. 125 * @throws java.io.IOException If there is any problem writing out the ZIP file. 126 * @see java.util.zip.ZipOutputStream 127 */ 128 public static void writeZip(File fileOrDir, File zipFile, int zipLevel) throws IOException { 129 requireNonNull(fileOrDir, "fileOrDir == null"); 130 requireNonNull(zipFile, "zipFile == null"); 131 try (FileOutputStream out = new FileOutputStream(zipFile)) { 132 writeZip(fileOrDir, out, zipLevel); 133 } 134 } 135 136 /** 137 * Create a ZIP archive file made up of all the contents of the specified file or 138 * directory. If any of the files in the specified directory contain characters that 139 * are illegal on some file systems, then those files are skipped and not included in 140 * the archive. 141 * 142 * @param fileOrDir The file or directory to be compressed into a ZIP archive. May not 143 * be null. 144 * @param zipFile The ZIP file to be written out. May not be null. 145 * @throws java.io.IOException If there is any problem writing out the ZIP file. 146 */ 147 public static void writeZip(File fileOrDir, File zipFile) throws IOException { 148 requireNonNull(fileOrDir, "fileOrDir == null"); 149 requireNonNull(zipFile, "zipFile == null"); 150 writeZip(fileOrDir, zipFile, java.util.zip.Deflater.DEFAULT_COMPRESSION); 151 } 152 153 /** 154 * Extracts a ZIP archive file to the specified directory. If the ZIP archive contains 155 * files with characters that might be illegal on some file systems, those characters 156 * are replaced with underline characters, '_'. 157 * 158 * @param input An InputStream from a ZIP archive. May not be null. 159 * @param outDir The directory to extract the ZIP file into. May not be null. 160 * @throws java.io.IOException If there is any problem extracting from the ZIP stream. 161 */ 162 public static void extractZip(InputStream input, File outDir) throws IOException { 163 requireNonNull(input, "input == null"); 164 requireNonNull(outDir, "outDir == null"); 165 166 try (ZipInputStream zis = new ZipInputStream(input)) { 167 ZipEntry entry; 168 while ((entry = zis.getNextEntry()) != null) { 169 // Sanitize the file name to remove any potentially illegal characters. 170 String name = cleanFileName(entry.getName()); 171 172 if (entry.isDirectory()) { 173 // Create the directory. 174 File d = new File(outDir, name); 175 if (!d.exists()) 176 d.mkdir(); 177 178 } else { 179 // Make sure that the directory for the file exists. 180 File file = new File(outDir, name); 181 File dir = file.getParentFile(); 182 if (dir != null && !dir.exists()) 183 dir.mkdir(); 184 185 // Write out the file. 186 try (FileOutputStream fos = new FileOutputStream(file)) { 187 byte buffer[] = new byte[BUFFER_SIZE]; 188 int count; 189 while ((count = zis.read(buffer)) != -1) 190 fos.write(buffer, 0, count); 191 } 192 } 193 } 194 } 195 196 } 197 198 /** 199 * Extracts a ZIP archive file to the specified directory. If the ZIP archive contains 200 * files with characters that might be illegal on some file systems, those characters 201 * are replaced with underline characters, '_'. 202 * 203 * 204 * @param zipFile The ZIP file to be extracted. May not be null. 205 * @param outDir The directory to extract the ZIP file into. May not be null. 206 * @throws java.io.IOException If there is any problem extracting the ZIP file. 207 */ 208 public static void extractZip(File zipFile, File outDir) throws IOException { 209 requireNonNull(zipFile, "zipFile == null"); 210 requireNonNull(outDir, "outDir == null"); 211 212 try (FileInputStream fis = new FileInputStream(zipFile)) { 213 extractZip(fis, outDir); 214 } 215 } 216 217 /** 218 * Add the specified directory, and all of it's contents, to the specified ZIP archive 219 * file. 220 * 221 * @param rootPath The path, in the ZIP archive, of the directory containing this 222 * directory (including the trailing "/"). <code>null</code> or "" 223 * will place this directory at the top of the ZIP archive. 224 * @param dir The directory to be added to the ZIP archive. 225 * @param zos An output stream pointing to the ZIP archive file. 226 * @param buffer A byte buffer used as temporary storage to read/write the data. 227 */ 228 private static void addDir(String rootPath, File dir, ZipOutputStream zos, byte[] buffer) throws IOException { 229 if (rootPath == null) 230 rootPath = ""; 231 232 // Get a listing of the directory contents 233 File[] dirList = dir.listFiles(); 234 235 // Loop through the directory listing, and zip all the files. 236 for (File f : dirList) { 237 if (f.isDirectory()) { 238 // If the File object is a directory, call this 239 // function again to add its content recursively. 240 241 // Create a new zip entry for a directory (they end in "/"). 242 rootPath += cleanFileName(f.getName()) + "/"; 243 ZipEntry anEntry = new ZipEntry(rootPath); 244 245 // Place the zip entry in the ZipOutputStream object 246 zos.putNextEntry(anEntry); 247 248 // Call this method again on the sub-directory. 249 addDir(rootPath, f, zos, buffer); 250 251 } else { 252 // If we reached here, the File object f was not a directory 253 addFile(rootPath, f, zos, buffer); 254 } 255 } 256 257 } 258 259 /** 260 * Add the specified file to the specified ZIP archive. Files with character names 261 * that may be illegal on some file systems will be skipped. 262 * 263 * @param rootPath The path, in the ZIP archive, of the directory containing this file 264 * (including the trailing "/"). <code>null</code> or "" will place 265 * this file at the top of the ZIP archive. 266 * 267 * @param file The file to be added to the ZIP archive. 268 * @param zos An output stream pointing to the ZIP archive file. 269 * @param buffer A byte buffer used as temporary storage to read/write the data. 270 */ 271 private static void addFile(String rootPath, File file, ZipOutputStream zos, byte[] buffer) throws IOException { 272 273 // If the file name contains illegal characters, just skip it. 274 if (hasIllegalChars(file.getName())) 275 return; 276 277 // Create an input stream from the file. 278 try (FileInputStream fis = new FileInputStream(file)) { 279 // Create a new zip entry 280 if (rootPath == null) 281 rootPath = ""; 282 ZipEntry anEntry = new ZipEntry(rootPath + file.getName()); 283 284 // Place the zip entry in the ZipOutputStream object 285 zos.putNextEntry(anEntry); 286 287 // Now write the content of the file to the ZipOutputStream 288 int bytesIn; 289 while ((bytesIn = fis.read(buffer)) != -1) 290 zos.write(buffer, 0, bytesIn); 291 } 292 293 } 294 295}