1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.codehaus.plexus.archiver;
18
19 import java.io.File;
20 import java.io.FileNotFoundException;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.nio.file.Files;
24 import java.nio.file.Path;
25 import java.util.ArrayList;
26 import java.util.Date;
27 import java.util.List;
28 import java.util.Locale;
29 import java.util.concurrent.atomic.AtomicInteger;
30
31 import org.codehaus.plexus.archiver.util.ArchiveEntryUtils;
32 import org.codehaus.plexus.components.io.attributes.SymlinkUtils;
33 import org.codehaus.plexus.components.io.filemappers.FileMapper;
34 import org.codehaus.plexus.components.io.fileselectors.FileSelector;
35 import org.codehaus.plexus.components.io.resources.PlexusIoResource;
36 import org.codehaus.plexus.util.FileUtils;
37 import org.codehaus.plexus.util.StringUtils;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
42
43
44
45
46
47
48 public abstract class AbstractUnArchiver implements UnArchiver, FinalizerEnabled {
49 private final Logger logger = LoggerFactory.getLogger(getClass());
50
51 protected Logger getLogger() {
52 return logger;
53 }
54
55 private File destDirectory;
56
57 private File destFile;
58
59 private File sourceFile;
60
61 private boolean overwrite = true;
62
63 private FileMapper[] fileMappers;
64
65 private List<ArchiveFinalizer> finalizers;
66
67 private FileSelector[] fileSelectors;
68
69
70
71
72
73
74 private boolean useJvmChmod = true;
75
76
77
78
79 private boolean ignorePermissions = false;
80
81 public AbstractUnArchiver() {
82
83 }
84
85 public AbstractUnArchiver(final File sourceFile) {
86 this.sourceFile = sourceFile;
87 }
88
89 @Override
90 public File getDestDirectory() {
91 return destDirectory;
92 }
93
94 @Override
95 public void setDestDirectory(final File destDirectory) {
96 this.destDirectory = destDirectory;
97 }
98
99 @Override
100 public File getDestFile() {
101 return destFile;
102 }
103
104 @Override
105 public void setDestFile(final File destFile) {
106 this.destFile = destFile;
107 }
108
109 @Override
110 public File getSourceFile() {
111 return sourceFile;
112 }
113
114 @Override
115 public void setSourceFile(final File sourceFile) {
116 this.sourceFile = sourceFile;
117 }
118
119 @Override
120 public boolean isOverwrite() {
121 return overwrite;
122 }
123
124 @Override
125 public void setOverwrite(final boolean b) {
126 overwrite = b;
127 }
128
129 @Override
130 public FileMapper[] getFileMappers() {
131 return fileMappers;
132 }
133
134 @Override
135 public void setFileMappers(final FileMapper[] fileMappers) {
136 this.fileMappers = fileMappers;
137 }
138
139 @Override
140 public final void extract() throws ArchiverException {
141 validate();
142 execute();
143 runArchiveFinalizers();
144 }
145
146 @Override
147 public final void extract(final String path, final File outputDirectory) throws ArchiverException {
148 validate(path, outputDirectory);
149 execute(path, outputDirectory);
150 runArchiveFinalizers();
151 }
152
153 @Override
154 public void addArchiveFinalizer(final ArchiveFinalizer finalizer) {
155 if (finalizers == null) {
156 finalizers = new ArrayList<>();
157 }
158
159 finalizers.add(finalizer);
160 }
161
162 @Override
163 public void setArchiveFinalizers(final List<ArchiveFinalizer> archiveFinalizers) {
164 finalizers = archiveFinalizers;
165 }
166
167 private void runArchiveFinalizers() throws ArchiverException {
168 if (finalizers != null) {
169 for (ArchiveFinalizer finalizer : finalizers) {
170 finalizer.finalizeArchiveExtraction(this);
171 }
172 }
173 }
174
175 protected void validate(final String path, final File outputDirectory) {}
176
177 protected void validate() throws ArchiverException {
178 if (sourceFile == null) {
179 throw new ArchiverException("The source file isn't defined.");
180 }
181
182 if (sourceFile.isDirectory()) {
183 throw new ArchiverException("The source must not be a directory.");
184 }
185
186 if (!sourceFile.exists()) {
187 throw new ArchiverException("The source file " + sourceFile + " doesn't exist.");
188 }
189
190 if (destDirectory == null && destFile == null) {
191 throw new ArchiverException("The destination isn't defined.");
192 }
193
194 if (destDirectory != null && destFile != null) {
195 throw new ArchiverException("You must choose between a destination directory and a destination file.");
196 }
197
198 if (destDirectory != null && !destDirectory.isDirectory()) {
199 destFile = destDirectory;
200 destDirectory = null;
201 }
202
203 if (destFile != null && destFile.isDirectory()) {
204 destDirectory = destFile;
205 destFile = null;
206 }
207 }
208
209 @Override
210 public void setFileSelectors(final FileSelector[] fileSelectors) {
211 this.fileSelectors = fileSelectors;
212 }
213
214 @Override
215 public FileSelector[] getFileSelectors() {
216 return fileSelectors;
217 }
218
219 protected boolean isSelected(final String fileName, final PlexusIoResource fileInfo) throws ArchiverException {
220 if (fileSelectors != null) {
221 for (FileSelector fileSelector : fileSelectors) {
222 try {
223
224 if (!fileSelector.isSelected(fileInfo)) {
225 return false;
226 }
227 } catch (final IOException e) {
228 throw new ArchiverException(
229 "Failed to check, whether " + fileInfo.getName() + " is selected: " + e.getMessage(), e);
230 }
231 }
232 }
233 return true;
234 }
235
236 protected abstract void execute() throws ArchiverException;
237
238 protected abstract void execute(String path, File outputDirectory) throws ArchiverException;
239
240
241
242
243 @Override
244 public boolean isUseJvmChmod() {
245 return useJvmChmod;
246 }
247
248
249
250
251
252
253 @Override
254 public void setUseJvmChmod(final boolean useJvmChmod) {
255 this.useJvmChmod = useJvmChmod;
256 }
257
258
259
260
261 @Override
262 public boolean isIgnorePermissions() {
263 return ignorePermissions;
264 }
265
266
267
268
269 @Override
270 public void setIgnorePermissions(final boolean ignorePermissions) {
271 this.ignorePermissions = ignorePermissions;
272 }
273
274 protected void extractFile(
275 final File srcF,
276 final File dir,
277 final InputStream compressedInputStream,
278 String entryName,
279 final Date entryDate,
280 final boolean isDirectory,
281 final Integer mode,
282 String symlinkDestination,
283 final FileMapper[] fileMappers)
284 throws IOException, ArchiverException {
285 if (fileMappers != null) {
286 for (final FileMapper fileMapper : fileMappers) {
287 entryName = fileMapper.getMappedFileName(entryName);
288 }
289 }
290
291
292 final File targetFileName = FileUtils.resolveFile(dir, entryName);
293
294
295
296
297 Path canonicalDirPath = dir.getCanonicalFile().toPath();
298 Path canonicalDestPath = targetFileName.getCanonicalFile().toPath();
299
300 if (!canonicalDestPath.startsWith(canonicalDirPath)) {
301 throw new ArchiverException("Entry is outside of the target directory (" + entryName + ")");
302 }
303
304
305 if (StringUtils.isEmpty(symlinkDestination) && Files.isSymbolicLink(canonicalDestPath)) {
306 throw new ArchiverException("Entry is outside of the target directory (" + entryName + ")");
307 }
308
309 try {
310 if (!shouldExtractEntry(dir, targetFileName, entryName, entryDate)) {
311 return;
312 }
313
314
315 final File dirF = targetFileName.getParentFile();
316 if (dirF != null) {
317 dirF.mkdirs();
318 }
319
320 if (!StringUtils.isEmpty(symlinkDestination)) {
321 SymlinkUtils.createSymbolicLink(targetFileName, new File(symlinkDestination));
322 } else if (isDirectory) {
323 targetFileName.mkdirs();
324 } else {
325 Files.copy(compressedInputStream, targetFileName.toPath(), REPLACE_EXISTING);
326 }
327
328 targetFileName.setLastModified(entryDate.getTime());
329
330 if (!isIgnorePermissions() && mode != null && !isDirectory) {
331 ArchiveEntryUtils.chmod(targetFileName, mode);
332 }
333 } catch (final FileNotFoundException ex) {
334 getLogger().warn("Unable to expand to file " + targetFileName.getPath());
335 }
336 }
337
338
339
340
341 final AtomicInteger casingMessageEmitted = new AtomicInteger(0);
342
343
344 protected boolean shouldExtractEntry(File targetDirectory, File targetFileName, String entryName, Date entryDate)
345 throws IOException {
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363 if (!targetFileName.exists()) {
364 return true;
365 }
366
367 boolean entryIsDirectory =
368 entryName.endsWith("/");
369 String canonicalDestPath = targetFileName.getCanonicalPath();
370 String suffix = (entryIsDirectory ? "/" : "");
371 String relativeCanonicalDestPath =
372 canonicalDestPath.replace(targetDirectory.getCanonicalPath() + File.separatorChar, "") + suffix;
373 boolean fileOnDiskIsOlderThanEntry = targetFileName.lastModified() < entryDate.getTime();
374 boolean differentCasing =
375 !normalizedFileSeparator(entryName).equals(normalizedFileSeparator(relativeCanonicalDestPath));
376
377
378 if (differentCasing) {
379 String casingMessage = String.format(
380 Locale.ENGLISH,
381 "Archive entry '%s' and existing file '%s' names differ only by case."
382 + " This may lead to an unexpected outcome on case-insensitive filesystems.",
383 entryName,
384 canonicalDestPath);
385 getLogger().warn(casingMessage);
386 casingMessageEmitted.incrementAndGet();
387 }
388
389
390
391 return isOverwrite() || fileOnDiskIsOlderThanEntry;
392 }
393
394 private String normalizedFileSeparator(String pathOrEntry) {
395 return pathOrEntry.replace("/", File.separator);
396 }
397 }