1   
2   
3   
4   
5   
6   
7   
8   
9   
10  
11  
12  
13  
14  
15  
16  
17  package org.codehaus.plexus.archiver.jar;
18  
19  import javax.inject.Named;
20  
21  import java.io.ByteArrayInputStream;
22  import java.io.ByteArrayOutputStream;
23  import java.io.File;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.io.OutputStreamWriter;
27  import java.io.PrintWriter;
28  import java.nio.charset.StandardCharsets;
29  import java.util.ArrayList;
30  import java.util.Collections;
31  import java.util.Comparator;
32  import java.util.Enumeration;
33  import java.util.HashSet;
34  import java.util.List;
35  import java.util.Set;
36  import java.util.SortedMap;
37  import java.util.StringTokenizer;
38  import java.util.TreeMap;
39  
40  import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
41  import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
42  import org.apache.commons.compress.archivers.zip.ZipFile;
43  import org.apache.commons.compress.parallel.InputStreamSupplier;
44  import org.codehaus.plexus.archiver.ArchiverException;
45  import org.codehaus.plexus.archiver.zip.ConcurrentJarCreator;
46  import org.codehaus.plexus.archiver.zip.ZipArchiver;
47  
48  import static org.codehaus.plexus.archiver.util.Streams.bufferedOutputStream;
49  import static org.codehaus.plexus.archiver.util.Streams.fileInputStream;
50  import static org.codehaus.plexus.archiver.util.Streams.fileOutputStream;
51  
52  
53  
54  
55  @Named("jar")
56  public class JarArchiver extends ZipArchiver {
57  
58      
59  
60  
61      private static final String META_INF_NAME = "META-INF";
62  
63      
64  
65  
66  
67  
68      @Deprecated
69      private static final String INDEX_NAME = "META-INF/INDEX.LIST";
70  
71      
72  
73  
74      private static final String MANIFEST_NAME = "META-INF/MANIFEST.MF";
75  
76      
77  
78  
79      private Manifest configuredManifest;
80  
81      
82  
83  
84      private Manifest savedConfiguredManifest;
85  
86      
87  
88  
89      private Manifest filesetManifest;
90  
91      
92  
93  
94  
95      private Manifest originalManifest;
96  
97      
98  
99  
100 
101     private FilesetManifestConfig filesetManifestConfig;
102 
103     
104 
105 
106 
107     private boolean mergeManifestsMain = true;
108 
109     
110 
111 
112     private Manifest manifest;
113 
114     
115 
116 
117 
118 
119 
120     private File manifestFile;
121 
122     
123 
124 
125 
126 
127     @Deprecated
128     private boolean index = false;
129 
130     
131 
132 
133 
134     private boolean createEmpty = false;
135 
136     
137 
138 
139 
140 
141 
142 
143     private final List<String> rootEntries;
144 
145     
146 
147 
148 
149 
150     @Deprecated
151     private List<String> indexJars;
152 
153     
154 
155 
156     private boolean minimalDefaultManifest = false;
157 
158     
159 
160 
161     public JarArchiver() {
162         super();
163         archiveType = "jar";
164         setEncoding("UTF8");
165         rootEntries = new ArrayList<>();
166     }
167 
168     
169 
170 
171 
172 
173 
174 
175     @Deprecated
176     public void setIndex(boolean flag) {
177         index = flag;
178     }
179 
180     
181 
182 
183 
184 
185     public void setMinimalDefaultManifest(boolean minimalDefaultManifest) {
186         this.minimalDefaultManifest = minimalDefaultManifest;
187     }
188 
189     @SuppressWarnings({"JavaDoc", "UnusedDeclaration"})
190     @Deprecated 
191     public void setManifestEncoding(String manifestEncoding) {}
192 
193     
194 
195 
196 
197 
198 
199 
200 
201     public void addConfiguredManifest(Manifest newManifest) throws ManifestException {
202         if (configuredManifest == null) {
203             configuredManifest = newManifest;
204         } else {
205             JdkManifestFactory.merge(configuredManifest, newManifest, false);
206         }
207         savedConfiguredManifest = configuredManifest;
208     }
209 
210     
211 
212 
213 
214 
215 
216 
217 
218     @SuppressWarnings({"UnusedDeclaration"})
219     public void setManifest(File manifestFile) throws ArchiverException {
220         if (!manifestFile.exists()) {
221             throw new ArchiverException("Manifest file: " + manifestFile + " does not exist.");
222         }
223 
224         this.manifestFile = manifestFile;
225     }
226 
227     private Manifest getManifest(File manifestFile) throws ArchiverException {
228         try (InputStream in = fileInputStream(manifestFile)) {
229             return getManifest(in);
230         } catch (IOException e) {
231             throw new ArchiverException(
232                     "Unable to read manifest file: " + manifestFile + " (" + e.getMessage() + ")", e);
233         }
234     }
235 
236     private Manifest getManifest(InputStream is) throws ArchiverException {
237         try {
238             return new Manifest(is);
239         } catch (IOException e) {
240             throw new ArchiverException("Unable to read manifest file" + " (" + e.getMessage() + ")", e);
241         }
242     }
243 
244     
245 
246 
247 
248 
249 
250 
251 
252 
253 
254 
255 
256 
257     @SuppressWarnings({"UnusedDeclaration"})
258     public void setFilesetmanifest(FilesetManifestConfig config) {
259         filesetManifestConfig = config;
260         mergeManifestsMain = FilesetManifestConfig.merge == config;
261 
262         if ((filesetManifestConfig != null) && filesetManifestConfig != FilesetManifestConfig.skip) {
263 
264             doubleFilePass = true;
265         }
266     }
267 
268     
269 
270 
271 
272     @Deprecated
273     public void addConfiguredIndexJars(File indexJar) {
274         if (indexJars == null) {
275             indexJars = new ArrayList<>();
276         }
277         indexJars.add(indexJar.getAbsolutePath());
278     }
279 
280     @Override
281     protected void initZipOutputStream(ConcurrentJarCreator zOut) throws ArchiverException, IOException {
282         if (!skipWriting) {
283             Manifest jarManifest = createManifest();
284             writeManifest(zOut, jarManifest);
285         }
286     }
287 
288     @Override
289     protected boolean hasVirtualFiles() {
290         getLogger().debug("\n\n\nChecking for jar manifest virtual files...\n\n\n");
291         System.out.flush();
292 
293         return (configuredManifest != null) || (manifest != null) || (manifestFile != null) || super.hasVirtualFiles();
294     }
295 
296     
297 
298 
299 
300 
301 
302 
303 
304 
305     protected Manifest createManifest() throws ArchiverException {
306         Manifest finalManifest = Manifest.getDefaultManifest(minimalDefaultManifest);
307 
308         if ((manifest == null) && (manifestFile != null)) {
309             
310             
311             manifest = getManifest(manifestFile);
312         }
313 
314         
315 
316 
317 
318 
319 
320 
321         if (isInUpdateMode()) {
322             JdkManifestFactory.merge(finalManifest, originalManifest, false);
323         }
324         JdkManifestFactory.merge(finalManifest, filesetManifest, false);
325         JdkManifestFactory.merge(finalManifest, configuredManifest, false);
326         JdkManifestFactory.merge(finalManifest, manifest, !mergeManifestsMain);
327 
328         return finalManifest;
329     }
330 
331     private void writeManifest(ConcurrentJarCreator zOut, Manifest manifest) throws IOException, ArchiverException {
332         for (Enumeration<String> e = manifest.getWarnings(); e.hasMoreElements(); ) {
333             getLogger().warn("Manifest warning: " + e.nextElement());
334         }
335 
336         zipDir(null, zOut, "META-INF/", DEFAULT_DIR_MODE, getEncoding());
337 
338         
339         ByteArrayOutputStream baos = new ByteArrayOutputStream(128);
340         manifest.write(baos);
341         InputStreamSupplier in = () -> new ByteArrayInputStream(baos.toByteArray());
342 
343         super.zipFile(in, zOut, MANIFEST_NAME, System.currentTimeMillis(), null, DEFAULT_FILE_MODE, null, false);
344         super.initZipOutputStream(zOut);
345     }
346 
347     @Override
348     protected void finalizeZipOutputStream(ConcurrentJarCreator zOut) throws IOException, ArchiverException {
349         if (index) {
350             createIndexList(zOut);
351         }
352     }
353 
354     
355 
356 
357 
358 
359 
360 
361 
362 
363 
364 
365 
366 
367     @Deprecated
368     private void createIndexList(ConcurrentJarCreator zOut) throws IOException, ArchiverException {
369         ByteArrayOutputStream baos = new ByteArrayOutputStream(128);
370         
371         PrintWriter writer = new PrintWriter(new OutputStreamWriter(baos, StandardCharsets.UTF_8));
372 
373         
374         writer.println("JarIndex-Version: 1.0");
375         writer.println();
376 
377         
378         writer.println(getDestFile().getName());
379 
380         
381         
382         Set<String> filteredDirs = addedDirs.allAddedDirs();
383         
384         if (filteredDirs.contains(META_INF_NAME + '/')) {
385             boolean add = false;
386             for (String entry : entries.keySet()) {
387                 if (entry.startsWith(META_INF_NAME + '/')
388                         && !entry.equals(INDEX_NAME)
389                         && !entry.equals(MANIFEST_NAME)) {
390                     add = true;
391                     break;
392                 }
393             }
394             if (!add) {
395                 filteredDirs.remove(META_INF_NAME + '/');
396             }
397         }
398         writeIndexLikeList(new ArrayList<>(filteredDirs), rootEntries, writer);
399         writer.println();
400 
401         if (indexJars != null) {
402             java.util.jar.Manifest mf = createManifest();
403             String classpath = mf.getMainAttributes().getValue(ManifestConstants.ATTRIBUTE_CLASSPATH);
404             String[] cpEntries = null;
405             if (classpath != null) {
406                 StringTokenizer tok = new StringTokenizer(classpath, " ");
407                 cpEntries = new String[tok.countTokens()];
408                 int c = 0;
409                 while (tok.hasMoreTokens()) {
410                     cpEntries[c++] = tok.nextToken();
411                 }
412             }
413 
414             for (String indexJar : indexJars) {
415                 String name = findJarName(indexJar, cpEntries);
416                 if (name != null) {
417                     List<String> dirs = new ArrayList<>();
418                     List<String> files = new ArrayList<>();
419                     grabFilesAndDirs(indexJar, dirs, files);
420                     if (dirs.size() + files.size() > 0) {
421                         writer.println(name);
422                         writeIndexLikeList(dirs, files, writer);
423                         writer.println();
424                     }
425                 }
426             }
427         }
428 
429         writer.flush();
430 
431         InputStreamSupplier in = () -> new ByteArrayInputStream(baos.toByteArray());
432 
433         super.zipFile(in, zOut, INDEX_NAME, System.currentTimeMillis(), null, DEFAULT_FILE_MODE, null, true);
434     }
435 
436     
437 
438 
439     @Override
440     protected void zipFile(
441             InputStreamSupplier is,
442             ConcurrentJarCreator zOut,
443             String vPath,
444             long lastModified,
445             File fromArchive,
446             int mode,
447             String symlinkDestination,
448             boolean addInParallel)
449             throws IOException, ArchiverException {
450         if (MANIFEST_NAME.equalsIgnoreCase(vPath)) {
451             if (!doubleFilePass || skipWriting) {
452                 try (InputStream manifestInputStream = is.get()) {
453                     filesetManifest(fromArchive, manifestInputStream);
454                 }
455             }
456         } else if (INDEX_NAME.equalsIgnoreCase(vPath) && index) {
457             getLogger()
458                     .warn("Warning: selected " + archiveType + " files include a META-INF/INDEX.LIST which will"
459                             + " be replaced by a newly generated one.");
460         } else {
461             if (index && !vPath.contains("/")) {
462                 rootEntries.add(vPath);
463             }
464             super.zipFile(is, zOut, vPath, lastModified, fromArchive, mode, symlinkDestination, addInParallel);
465         }
466     }
467 
468     private void filesetManifest(File file, InputStream is) throws ArchiverException {
469         if ((manifestFile != null) && manifestFile.equals(file)) {
470             
471             
472             getLogger().debug("Found manifest " + file);
473             if (is != null) {
474                 manifest = getManifest(is);
475             } else {
476                 manifest = getManifest(file);
477             }
478         } else if ((filesetManifestConfig != null) && filesetManifestConfig != FilesetManifestConfig.skip) {
479             
480             getLogger().debug("Found manifest to merge in file " + file);
481 
482             Manifest newManifest;
483             if (is != null) {
484                 newManifest = getManifest(is);
485             } else {
486                 newManifest = getManifest(file);
487             }
488 
489             if (filesetManifest == null) {
490                 filesetManifest = newManifest;
491             } else {
492                 JdkManifestFactory.merge(filesetManifest, newManifest, false);
493             }
494         }
495     }
496 
497     @Override
498     protected boolean createEmptyZip(File zipFile) throws ArchiverException {
499         if (!createEmpty) {
500             return true;
501         }
502 
503         try {
504             getLogger().debug("Building MANIFEST-only jar: " + getDestFile().getAbsolutePath());
505             zipArchiveOutputStream =
506                     new ZipArchiveOutputStream(bufferedOutputStream(fileOutputStream(getDestFile(), "jar")));
507 
508             zipArchiveOutputStream.setEncoding(getEncoding());
509             if (isCompress()) {
510                 zipArchiveOutputStream.setMethod(ZipArchiveOutputStream.DEFLATED);
511             } else {
512                 zipArchiveOutputStream.setMethod(ZipArchiveOutputStream.STORED);
513             }
514             ConcurrentJarCreator ps = new ConcurrentJarCreator(
515                     isRecompressAddedZips(), Runtime.getRuntime().availableProcessors());
516             initZipOutputStream(ps);
517             finalizeZipOutputStream(ps);
518         } catch (IOException ioe) {
519             throw new ArchiverException("Could not create almost empty JAR archive (" + ioe.getMessage() + ")", ioe);
520         } finally {
521             
522             
523             createEmpty = false;
524         }
525         return true;
526     }
527 
528     
529 
530 
531 
532 
533 
534     @Override
535     protected void cleanUp() throws IOException {
536         super.cleanUp();
537 
538         
539         if (!doubleFilePass || !skipWriting) {
540             manifest = null;
541             configuredManifest = savedConfiguredManifest;
542             filesetManifest = null;
543             originalManifest = null;
544         }
545         rootEntries.clear();
546     }
547 
548     
549 
550 
551 
552 
553     @Override
554     public void reset() {
555         super.reset();
556         configuredManifest = null;
557         filesetManifestConfig = null;
558         mergeManifestsMain = false;
559         manifestFile = null;
560         index = false;
561     }
562 
563     public enum FilesetManifestConfig {
564         skip,
565         merge,
566         mergewithoutmain
567     }
568 
569     
570 
571 
572 
573 
574 
575 
576 
577 
578     @Deprecated
579     protected final void writeIndexLikeList(List<String> dirs, List<String> files, PrintWriter writer) {
580         
581         
582         
583         Collections.sort(dirs);
584         Collections.sort(files);
585         for (String dir : dirs) {
586             
587             dir = dir.replace('\\', '/');
588             if (dir.startsWith("./")) {
589                 dir = dir.substring(2);
590             }
591             while (dir.startsWith("/")) {
592                 dir = dir.substring(1);
593             }
594             int pos = dir.lastIndexOf('/');
595             if (pos != -1) {
596                 dir = dir.substring(0, pos);
597             }
598 
599             
600             writer.println(dir);
601         }
602 
603         for (String file : files) {
604             writer.println(file);
605         }
606     }
607 
608     
609 
610 
611 
612 
613 
614 
615 
616 
617 
618 
619 
620 
621 
622 
623 
624 
625 
626 
627 
628     protected static String findJarName(String fileName, String[] classpath) {
629         if (classpath == null) {
630             return new File(fileName).getName();
631         }
632         fileName = fileName.replace(File.separatorChar, '/');
633 
634         
635         SortedMap<String, String> matches =
636                 new TreeMap<>(Comparator.comparingInt(String::length).reversed());
637 
638         for (String aClasspath : classpath) {
639             if (fileName.endsWith(aClasspath)) {
640                 matches.put(aClasspath, aClasspath);
641             } else {
642                 int slash = aClasspath.indexOf("/");
643                 String candidate = aClasspath;
644                 while (slash > -1) {
645                     candidate = candidate.substring(slash + 1);
646                     if (fileName.endsWith(candidate)) {
647                         matches.put(candidate, aClasspath);
648                         break;
649                     }
650                     slash = candidate.indexOf("/");
651                 }
652             }
653         }
654 
655         return matches.size() == 0 ? null : matches.get(matches.firstKey());
656     }
657 
658     
659 
660 
661 
662 
663 
664 
665 
666 
667 
668     private void grabFilesAndDirs(String file, List<String> dirs, List<String> files) throws IOException {
669         File zipFile = new File(file);
670         if (!zipFile.exists()) {
671             getLogger().error("JarArchive skipping non-existing file: " + zipFile.getAbsolutePath());
672         } else if (zipFile.isDirectory()) {
673             getLogger().info("JarArchiver skipping indexJar " + zipFile + " because it is not a jar");
674         } else {
675             try (ZipFile zf = ZipFile.builder()
676                     .setFile(file)
677                     .setCharset(StandardCharsets.UTF_8)
678                     .get()) {
679                 Enumeration<ZipArchiveEntry> entries = zf.getEntries();
680                 HashSet<String> dirSet = new HashSet<>();
681                 while (entries.hasMoreElements()) {
682                     ZipArchiveEntry ze = entries.nextElement();
683                     String name = ze.getName();
684                     
685                     if (!name.equals(META_INF_NAME)
686                             && !name.equals(META_INF_NAME + '/')
687                             && !name.equals(INDEX_NAME)
688                             && !name.equals(MANIFEST_NAME)) {
689                         if (ze.isDirectory()) {
690                             dirSet.add(name);
691                         } else if (!name.contains("/")) {
692                             files.add(name);
693                         } else {
694                             
695                             
696                             
697                             
698                             dirSet.add(name.substring(0, name.lastIndexOf("/") + 1));
699                         }
700                     }
701                 }
702                 dirs.addAll(dirSet);
703             }
704         }
705     }
706 
707     
708 
709 
710 
711 
712 
713     @Override
714     protected void setZipEntryTime(ZipArchiveEntry zipEntry, long lastModifiedTime) {
715         if (getLastModifiedTime() != null) {
716             lastModifiedTime = getLastModifiedTime().toMillis();
717         }
718 
719         
720         zipEntry.setTime(lastModifiedTime);
721     }
722 }