View Javadoc
1   /*
2    * Copyright 2024 Bloomreach B.V. (https://www.bloomreach.com)
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.onehippo.forge.content.exim.repository.jaxrs.util;
17  
18  import java.io.File;
19  import java.io.FileInputStream;
20  import java.io.IOException;
21  import java.io.InputStream;
22  
23  import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
24  import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
25  import org.apache.commons.io.IOUtils;
26  import org.apache.commons.lang3.StringUtils;
27  
28  /**
29   * ZIP Compressing Utilities.
30   *
31   * Note: When using ZipArchiveOutputStream, ensure that Unicode filename support is enabled via:
32   * {@code zipOutput.setCreateUnicodeExtraFields(ZipArchiveOutputStream.UnicodeExtraFieldPolicy.ALWAYS)}
33   * This is necessary to properly preserve non-ASCII characters (e.g., Cyrillic) in ZIP entry names.
34   * See FORGE-448 for details.
35   */
36  public class ZipCompressUtils {
37  
38      private ZipCompressUtils() {
39      }
40  
41      /**
42       * Add a ZIP entry to {@code zipOutput} with the given {@code entryName} and string {@code content} in
43       * {@code charsetName} encoding.
44       * @param entryName ZIP entry name
45       * @param content string content of the ZIP entry.
46       * @param charsetName charset name in encoding
47       * @param zipOutput ZipArchiveOutputStream instance
48       * @throws IOException if IO exception occurs
49       */
50      public static void addEntryToZip(String entryName, String content, String charsetName,
51              ZipArchiveOutputStream zipOutput) throws IOException {
52          byte[] bytes;
53  
54          if (StringUtils.isBlank(charsetName)) {
55              bytes = content.getBytes();
56          } else {
57              bytes = content.getBytes(charsetName);
58          }
59  
60          addEntryToZip(entryName, bytes, zipOutput);
61      }
62  
63      /**
64       * Add a ZIP entry to {@code zipOutput} with the given {@code entryName} and {@code bytes}.
65       * @param entryName ZIP entry name
66       * @param bytes the byte array to fill in for the ZIP entry
67       * @param zipOutput ZipArchiveOutputStream instance
68       * @throws IOException if IO exception occurs
69       */
70      public static void addEntryToZip(String entryName, byte[] bytes,
71              ZipArchiveOutputStream zipOutput) throws IOException {
72          addEntryToZip(entryName, bytes, 0, bytes.length, zipOutput);
73      }
74  
75      /**
76       * Add a ZIP entry to {@code zipOutput} with the given {@code entryName} and {@code bytes} starting from
77       * {@code offset} in {@code length}.
78       * @param entryName ZIP entry name
79       * @param bytes the byte array to fill in for the ZIP entry
80       * @param offset the starting offset index to read from the byte array
81       * @param length the length to read from the byte array
82       * @param zipOutput ZipArchiveOutputStream instance
83       * @throws IOException if IO exception occurs
84       */
85      public static void addEntryToZip(String entryName, byte[] bytes, int offset, int length,
86              ZipArchiveOutputStream zipOutput) throws IOException {
87          ZipArchiveEntry entry = new ZipArchiveEntry(entryName);
88          entry.setSize(length);
89  
90          try {
91              zipOutput.putArchiveEntry(entry);
92              zipOutput.write(bytes, offset, length);
93          } finally {
94              zipOutput.closeArchiveEntry();
95          }
96      }
97  
98      /**
99       * Add ZIP entries to {@code zipOutput} by selecting all the descendant files under the {@code baseFolder},
100      * starting with the ZIP entry name {@code prefix}.
101      * @param baseFolder base folder to find child files underneath
102      * @param prefix the prefix of ZIP entry name
103      * @param zipOutput ZipArchiveOutputStream instance
104      * @throws IOException if IO exception occurs
105      */
106     public static void addFileEntriesInFolderToZip(File baseFolder, String prefix, ZipArchiveOutputStream zipOutput)
107             throws IOException {
108         for (File file : baseFolder.listFiles()) {
109             String entryName = (StringUtils.isEmpty(prefix)) ? file.getName() : (prefix + "/" + file.getName());
110 
111             if (file.isFile()) {
112                 ZipArchiveEntry entry = new ZipArchiveEntry(entryName);
113                 entry.setSize(file.length());
114                 InputStream input = null;
115 
116                 try {
117                     zipOutput.putArchiveEntry(entry);
118                     input = new FileInputStream(file);
119                     IOUtils.copyLarge(input, zipOutput);
120                 } finally {
121                     IOUtils.closeQuietly(input);
122                     zipOutput.closeArchiveEntry();
123                 }
124             } else {
125                 addFileEntriesInFolderToZip(file, entryName, zipOutput);
126             }
127         }
128     }
129 }