1 package com.atlassian.plugin.util.zip;
2
3 import com.google.common.annotations.VisibleForTesting;
4 import org.apache.commons.io.FileUtils;
5 import org.apache.commons.io.FilenameUtils;
6 import org.apache.commons.io.IOUtils;
7 import org.apache.commons.lang3.StringUtils;
8 import org.slf4j.Logger;
9 import org.slf4j.LoggerFactory;
10
11 import java.io.File;
12 import java.io.FileNotFoundException;
13 import java.io.FileOutputStream;
14 import java.io.IOException;
15 import java.io.InputStream;
16 import java.util.ArrayList;
17 import java.util.Arrays;
18 import java.util.Collections;
19 import java.util.HashMap;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.zip.ZipEntry;
23 import java.util.zip.ZipInputStream;
24
25 import static java.util.stream.Collectors.toMap;
26
27 public abstract class AbstractUnzipper implements Unzipper {
28 protected static Logger log = LoggerFactory.getLogger(FileUnzipper.class);
29 protected File destDir;
30
31 protected File saveEntry(InputStream is, ZipEntry entry) throws IOException {
32 final File file = new File(destDir, normaliseAndVerify(entry.getName()));
33
34 if (entry.isDirectory()) {
35 file.mkdirs();
36 } else {
37 final File dir = new File(file.getParent());
38 dir.mkdirs();
39
40 FileOutputStream fos = null;
41 try {
42 fos = new FileOutputStream(file);
43 IOUtils.copy(is, fos);
44 fos.flush();
45 } catch (FileNotFoundException fnfe) {
46 log.error("Error extracting a file to '{}{}{}'. This destination is invalid for writing an extracted " +
47 "file stream to.", destDir, File.separator, entry.getName());
48 return null;
49 } finally {
50 IOUtils.closeQuietly(fos);
51 }
52 }
53 file.setLastModified(entry.getTime());
54
55 return file;
56 }
57
58 protected ZipEntry[] entries(ZipInputStream zis) throws IOException {
59 List<ZipEntry> entries = new ArrayList<>();
60 try {
61 ZipEntry zipEntry = zis.getNextEntry();
62 while (zipEntry != null) {
63 entries.add(zipEntry);
64 zis.closeEntry();
65 zipEntry = zis.getNextEntry();
66 }
67 } finally {
68 IOUtils.closeQuietly(zis);
69 }
70
71 return entries.toArray(new ZipEntry[0]);
72 }
73
74 public void conditionalUnzip() throws IOException {
75 Map<String, Long> zipContentsAndLastModified = Arrays.stream(entries())
76 .collect(toMap(ZipEntry::getName, ZipEntry::getTime));
77
78
79
80 Map<String, Long> targetDirContents = getContentsOfTargetDir(destDir);
81 if (!targetDirContents.equals(zipContentsAndLastModified)) {
82
83 if (destDir.exists()) {
84 FileUtils.cleanDirectory(destDir);
85 }
86 unzip();
87 } else {
88 log.debug("Target directory contents match zip contents. Do nothing.");
89 }
90 }
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106 @VisibleForTesting
107 static String normaliseAndVerify(String name) {
108 final String normalised = FilenameUtils.normalizeNoEndSeparator(name);
109 if (StringUtils.isBlank(normalised)) {
110 throw new IllegalArgumentException("Path name " + name + " is illegal");
111 }
112 return normalised;
113 }
114
115 private Map<String, Long> getContentsOfTargetDir(File dir) {
116 if (!dir.isDirectory()) {
117 return Collections.emptyMap();
118 }
119
120 File[] files = dir.listFiles();
121 if (files == null) {
122 return Collections.emptyMap();
123 }
124
125 Map<String, Long> targetDirContents = new HashMap<>();
126 for (File child : files) {
127 if (log.isDebugEnabled()) {
128 log.debug("Examining entry in zip: " + child);
129 }
130 targetDirContents.put(child.getName(), child.lastModified());
131 }
132
133 return targetDirContents;
134 }
135 }