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