View Javadoc
1   /*
2    * Copyright 2007 The Codehaus Foundation.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.codehaus.plexus.archiver.zip;
17  
18  import java.io.Closeable;
19  import java.io.File;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.net.URL;
23  import java.net.URLClassLoader;
24  import java.nio.charset.Charset;
25  import java.nio.charset.StandardCharsets;
26  import java.util.Enumeration;
27  import java.util.Iterator;
28  import java.util.jar.JarFile;
29  
30  import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
31  import org.apache.commons.compress.archivers.zip.ZipFile;
32  import org.codehaus.plexus.components.io.functions.SymlinkDestinationSupplier;
33  import org.codehaus.plexus.components.io.resources.AbstractPlexusIoArchiveResourceCollection;
34  import org.codehaus.plexus.components.io.resources.EncodingSupported;
35  import org.codehaus.plexus.components.io.resources.PlexusIoResource;
36  import org.codehaus.plexus.components.io.resources.PlexusIoURLResource;
37  
38  /**
39   * Zip file resource collection that uses JarFile for reading entries,
40   * which verifies JAR signatures when the file is signed.
41   * This is slower than {@link PlexusArchiverZipFileResourceCollection} but is necessary
42   * for signed JAR files to maintain security.
43   */
44  public class PlexusIoJarFileResourceCollectionWithSignatureVerification
45          extends AbstractPlexusIoArchiveResourceCollection implements EncodingSupported {
46  
47      private Charset charset = StandardCharsets.UTF_8;
48  
49      public PlexusIoJarFileResourceCollectionWithSignatureVerification() {}
50  
51      @Override
52      public boolean isConcurrentAccessSupported() {
53          // Maybe we could support concurrent some time in the future
54          return false;
55      }
56  
57      @Override
58      protected Iterator<PlexusIoResource> getEntries() throws IOException {
59          final File f = getFile();
60          if (f == null) {
61              throw new IOException("The zip file has not been set.");
62          }
63          final URLClassLoader urlClassLoader =
64                  new URLClassLoader(new URL[] {f.toURI().toURL()}, null) {
65  
66                      @Override
67                      public URL getResource(String name) {
68                          return findResource(name);
69                      }
70                  };
71  
72          final URL url = new URL("jar:" + f.toURI().toURL() + "!/");
73          final JarFile jarFile = new JarFile(f);
74          final ZipFile zipFile = ZipFile.builder()
75                  .setFile(f)
76                  .setCharset(charset != null ? charset : StandardCharsets.UTF_8)
77                  .get();
78          final Enumeration<ZipArchiveEntry> en = zipFile.getEntriesInPhysicalOrder();
79          return new ZipFileResourceIterator(en, url, jarFile, zipFile, urlClassLoader);
80      }
81  
82      private static class ZipFileResourceIterator implements Iterator<PlexusIoResource>, Closeable {
83  
84          private class ZipFileResource extends PlexusIoURLResource {
85              private final JarFile jarFile;
86  
87              private ZipFileResource(JarFile jarFile, ZipArchiveEntry entry) {
88                  super(
89                          entry.getName(),
90                          entry.getTime() == -1 ? PlexusIoResource.UNKNOWN_MODIFICATION_DATE : entry.getTime(),
91                          entry.isDirectory() ? PlexusIoResource.UNKNOWN_RESOURCE_SIZE : entry.getSize(),
92                          !entry.isDirectory(),
93                          entry.isDirectory(),
94                          true);
95  
96                  this.jarFile = jarFile;
97              }
98  
99              @Override
100             public InputStream getContents() throws IOException {
101                 // Uses the JarFile to get the input stream for the entry.
102                 // The super method will do the same, why overriding it?
103                 // But it will create new JarFile for every entry
104                 // and that could be very expensive in some cases (when the JAR is signed).
105                 // Enabling the URLConnection cache would solve the problem
106                 // but the cache is global and shared during the build so
107                 // if the AJR file changed during the causes another problem
108                 // (see plexus-io#2).
109                 // Using local JarFile instance solves the two issues -
110                 // JarFile is initialized once so there is no performance penalty
111                 // and it is local so if the file changed during the build
112                 // would not be a problem.
113                 // And we know the URL returned by getURL is part of the JAR
114                 // because that is how we constructed it.
115                 return jarFile.getInputStream(jarFile.getEntry(getName()));
116             }
117 
118             @Override
119             public URL getURL() throws IOException {
120                 String spec = getName();
121                 if (spec.startsWith("/")) {
122                     // Code path for PLXCOMP-170. Note that urlClassloader does not seem to produce correct
123                     // urls for this. Which again means files loaded via this path cannot have file names
124                     // requiring url encoding
125                     spec = "./" + spec;
126                     return new URL(url, spec);
127                 }
128                 return urlClassLoader.getResource(spec);
129             }
130         }
131 
132         private class ZipFileSymlinkResource extends ZipFileResource implements SymlinkDestinationSupplier {
133 
134             private final ZipArchiveEntry entry;
135 
136             private ZipFileSymlinkResource(JarFile jarFile, ZipArchiveEntry entry) {
137                 super(jarFile, entry);
138 
139                 this.entry = entry;
140             }
141 
142             @Override
143             public String getSymlinkDestination() throws IOException {
144                 return zipFile.getUnixSymlink(entry);
145             }
146 
147             @Override
148             public boolean isSymbolicLink() {
149                 return true;
150             }
151         }
152 
153         private final Enumeration<ZipArchiveEntry> en;
154 
155         private final URL url;
156 
157         private final JarFile jarFile;
158 
159         private final ZipFile zipFile;
160 
161         private final URLClassLoader urlClassLoader;
162 
163         public ZipFileResourceIterator(
164                 Enumeration<ZipArchiveEntry> en,
165                 URL url,
166                 JarFile jarFile,
167                 ZipFile zipFile,
168                 URLClassLoader urlClassLoader) {
169             this.en = en;
170             this.url = url;
171             this.jarFile = jarFile;
172             this.zipFile = zipFile;
173             this.urlClassLoader = urlClassLoader;
174         }
175 
176         @Override
177         public boolean hasNext() {
178             return en.hasMoreElements();
179         }
180 
181         @Override
182         public PlexusIoResource next() {
183             final ZipArchiveEntry entry = en.nextElement();
184             return entry.isUnixSymlink()
185                     ? new ZipFileSymlinkResource(jarFile, entry)
186                     : new ZipFileResource(jarFile, entry);
187         }
188 
189         @Override
190         public void remove() {
191             throw new UnsupportedOperationException("Removing isn't implemented.");
192         }
193 
194         @Override
195         public void close() throws IOException {
196             try {
197                 urlClassLoader.close();
198             } finally {
199                 try {
200                     zipFile.close();
201                 } finally {
202                     jarFile.close();
203                 }
204             }
205         }
206     }
207 
208     @Override
209     public void setEncoding(Charset charset) {
210         this.charset = charset;
211     }
212 }