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 @Deprecated
245 public boolean isUseJvmChmod() {
246 return useJvmChmod;
247 }
248
249
250
251
252
253
254 @Override
255 @Deprecated
256 public void setUseJvmChmod(final boolean useJvmChmod) {
257 this.useJvmChmod = useJvmChmod;
258 }
259
260
261
262
263 @Override
264 public boolean isIgnorePermissions() {
265 return ignorePermissions;
266 }
267
268
269
270
271 @Override
272 public void setIgnorePermissions(final boolean ignorePermissions) {
273 this.ignorePermissions = ignorePermissions;
274 }
275
276 protected void extractFile(
277 final File srcF,
278 final File dir,
279 final InputStream compressedInputStream,
280 String entryName,
281 final Date entryDate,
282 final boolean isDirectory,
283 final Integer mode,
284 String symlinkDestination,
285 final FileMapper[] fileMappers)
286 throws IOException, ArchiverException {
287 if (fileMappers != null) {
288 for (final FileMapper fileMapper : fileMappers) {
289 entryName = fileMapper.getMappedFileName(entryName);
290 }
291 }
292
293
294 final File targetFileName = FileUtils.resolveFile(dir, entryName);
295
296
297
298
299 Path canonicalDirPath = dir.getCanonicalFile().toPath();
300 Path canonicalDestPath = targetFileName.getCanonicalFile().toPath();
301
302 if (!canonicalDestPath.startsWith(canonicalDirPath)) {
303 throw new ArchiverException("Entry is outside of the target directory (" + entryName + ")");
304 }
305
306
307 if (StringUtils.isEmpty(symlinkDestination) && Files.isSymbolicLink(canonicalDestPath)) {
308 throw new ArchiverException("Entry is outside of the target directory (" + entryName + ")");
309 }
310
311 try {
312 if (!shouldExtractEntry(dir, targetFileName, entryName, entryDate)) {
313 return;
314 }
315
316
317 final File dirF = targetFileName.getParentFile();
318 if (dirF != null) {
319 dirF.mkdirs();
320 }
321
322 if (!StringUtils.isEmpty(symlinkDestination)) {
323 SymlinkUtils.createSymbolicLink(targetFileName, new File(symlinkDestination));
324 } else if (isDirectory) {
325 targetFileName.mkdirs();
326 } else {
327 Files.copy(compressedInputStream, targetFileName.toPath(), REPLACE_EXISTING);
328 }
329
330 targetFileName.setLastModified(entryDate.getTime());
331
332 if (!isIgnorePermissions() && mode != null && !isDirectory) {
333 ArchiveEntryUtils.chmod(targetFileName, mode);
334 }
335 } catch (final FileNotFoundException ex) {
336 getLogger().warn("Unable to expand to file " + targetFileName.getPath());
337 }
338 }
339
340
341
342
343 final AtomicInteger casingMessageEmitted = new AtomicInteger(0);
344
345
346 protected boolean shouldExtractEntry(File targetDirectory, File targetFileName, String entryName, Date entryDate)
347 throws IOException {
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365 if (!targetFileName.exists()) {
366 return true;
367 }
368
369 boolean entryIsDirectory =
370 entryName.endsWith("/");
371 String canonicalDestPath = targetFileName.getCanonicalPath();
372 String suffix = (entryIsDirectory ? "/" : "");
373 String relativeCanonicalDestPath =
374 canonicalDestPath.replace(targetDirectory.getCanonicalPath() + File.separatorChar, "") + suffix;
375 boolean fileOnDiskIsOlderThanEntry = targetFileName.lastModified() < entryDate.getTime();
376 boolean differentCasing =
377 !normalizedFileSeparator(entryName).equals(normalizedFileSeparator(relativeCanonicalDestPath));
378
379
380 if (differentCasing) {
381 String casingMessage = String.format(
382 Locale.ENGLISH,
383 "Archive entry '%s' and existing file '%s' names differ only by case."
384 + " This may lead to an unexpected outcome on case-insensitive filesystems.",
385 entryName,
386 canonicalDestPath);
387 getLogger().warn(casingMessage);
388 casingMessageEmitted.incrementAndGet();
389 }
390
391
392
393 return isOverwrite() || fileOnDiskIsOlderThanEntry;
394 }
395
396 private String normalizedFileSeparator(String pathOrEntry) {
397 return pathOrEntry.replace("/", File.separator);
398 }
399 }