View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.vfs2.provider.zip;
18  
19  import static org.junit.jupiter.api.Assertions.assertEquals;
20  import static org.junit.jupiter.api.Assertions.assertNotNull;
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.nio.charset.StandardCharsets;
26  import java.nio.file.Files;
27  import java.nio.file.Path;
28  import java.nio.file.Paths;
29  import java.nio.file.StandardCopyOption;
30  
31  import org.apache.commons.io.IOUtils;
32  import org.apache.commons.vfs2.FileObject;
33  import org.apache.commons.vfs2.FileSystemException;
34  import org.apache.commons.vfs2.FileSystemManager;
35  import org.apache.commons.vfs2.VFS;
36  import org.junit.jupiter.api.Disabled;
37  import org.junit.jupiter.api.Test;
38  
39  public class ZipFileObjectTest {
40  
41      private static final String NESTED_FILE_1 = "/read-xml-tests/file1.xml";
42      private static final String NESTED_FILE_2 = "/read-xml-tests/file2.xml";
43  
44      private void assertDelete(final Path fileObject) throws IOException {
45          Files.delete(fileObject);
46      }
47  
48      private Path createTempFile() throws IOException {
49          final Path zipFile = Paths.get("src/test/resources/test-data/read-xml-tests.zip");
50          final Path newZipFile = Files.createTempFile(getClass().getSimpleName(), ".zip");
51          newZipFile.toFile().deleteOnExit();
52          Files.copy(zipFile, newZipFile, StandardCopyOption.REPLACE_EXISTING);
53          return newZipFile;
54      }
55  
56      private void getInputStreamAndAssert(final FileObject fileObject, final String expectedId)
57              throws FileSystemException, IOException {
58          readAndAssert(fileObject, fileObject.getContent().getInputStream(), expectedId);
59      }
60  
61      private void readAndAssert(final FileObject fileObject, final InputStream inputStream, final String expectedId)
62              throws IOException {
63          final String streamData = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
64          final String fileObjectString = fileObject.toString();
65          assertNotNull(fileObjectString, streamData);
66          assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<Root" + expectedId + ">foo" + expectedId + "</Root" + expectedId + ">\r\n", streamData,
67                  fileObjectString);
68      }
69  
70      private void resolveReadAssert(final FileObject zipFileObject, final String path)
71              throws IOException, FileSystemException {
72          try (FileObject zipFileObject2 = zipFileObject.resolveFile(path)) {
73              try (InputStream inputStream = zipFileObject2.getContent().getInputStream()) {
74                  readAndAssert(zipFileObject2, inputStream, "2");
75              }
76          }
77      }
78  
79      /**
80       * Tests that when we read a file inside a file Zip and leave it open, we can still delete the Zip after we clean up
81       * the Zip file.
82       *
83       * @throws IOException
84       */
85      @Test
86      @Disabled("Shows that leaving a stream open and not closing any resource leaves the container file locked")
87      public void testLeaveNestedFileOpen() throws IOException {
88          final Path newZipFile = createTempFile();
89          final FileSystemManager manager = VFS.getManager();
90          try (FileObject zipFileObject = manager.resolveFile("zip:file:" + newZipFile.toAbsolutePath())) {
91              @SuppressWarnings({ "resource" })
92              final FileObject zipFileObject1 = zipFileObject.resolveFile(NESTED_FILE_1);
93              getInputStreamAndAssert(zipFileObject1, "1");
94          }
95          assertDelete(newZipFile);
96      }
97  
98      /**
99       * Tests that we can read more than one file within a Zip file, especially after closing each FileObject.
100      *
101      * @throws IOException
102      */
103     @Test
104     public void testReadingFilesInZipFile() throws IOException {
105         final Path newZipFile = createTempFile();
106         final FileSystemManager manager = VFS.getManager();
107         try (FileObject zipFileObject = manager.resolveFile("zip:file:" + newZipFile.toAbsolutePath())) {
108             try (FileObject zipFileObject1 = zipFileObject.resolveFile(NESTED_FILE_1)) {
109                 try (InputStream inputStream = zipFileObject1.getContent().getInputStream()) {
110                     readAndAssert(zipFileObject1, inputStream, "1");
111                 }
112             }
113             resolveReadAssert(zipFileObject, NESTED_FILE_2);
114         }
115         assertDelete(newZipFile);
116     }
117 
118     /**
119      * Tests that we can get a stream from one file in a ZIP file, then close another file from the same zip, then
120      * process the initial input stream.
121      *
122      * @throws IOException
123      */
124     @Test
125     public void testReadingOneAfterClosingAnotherFile() throws IOException {
126         final Path newZipFile = createTempFile();
127         final FileSystemManager manager = VFS.getManager();
128         final FileObject zipFileObject1;
129         final InputStream inputStream1;
130         try (FileObject zipFileObject = manager.resolveFile("zip:file:" + newZipFile.toAbsolutePath())) {
131             // leave resources open
132             zipFileObject1 = zipFileObject.resolveFile(NESTED_FILE_1);
133             inputStream1 = zipFileObject1.getContent().getInputStream();
134         }
135         // The zip file is "closed", but we read from the stream now.
136         readAndAssert(zipFileObject1, inputStream1, "1");
137         // clean up
138         zipFileObject1.close();
139         assertDelete(newZipFile);
140     }
141 
142     /**
143      * Tests that we can get a stream from one file in a ZIP file, then close another file from the same zip, then
144      * process the initial input stream. If our internal reference counting is correct, the test passes.
145      *
146      * @throws IOException
147      */
148     @Test
149     public void testReadingOneAfterClosingAnotherStream() throws IOException {
150         final Path newZipFile = createTempFile();
151         final FileSystemManager manager = VFS.getManager();
152         final FileObject zipFileObject1;
153         final InputStream inputStream1;
154         try (FileObject zipFileObject = manager.resolveFile("zip:file:" + newZipFile.toAbsolutePath())) {
155             // leave resources open (note that internal counters are updated)
156             zipFileObject1 = zipFileObject.resolveFile(NESTED_FILE_1);
157             inputStream1 = zipFileObject1.getContent().getInputStream();
158             resolveReadAssert(zipFileObject, NESTED_FILE_2);
159         }
160         // The Zip file is "closed", but we read from the stream now, which currently fails.
161         // Why aren't internal counters preventing the stream from closing?
162         readAndAssert(zipFileObject1, inputStream1, "1");
163         // clean up
164         zipFileObject1.close();
165         assertDelete(newZipFile);
166     }
167 
168     /**
169      * Test read file with special name in a ZIP file.
170      */
171     @Test
172     public void testReadSpecialNameFileInZipFile() throws FileSystemException {
173 
174         final File testFile = new File("src/test/resources/test-data/special_fileName.zip");
175         final String[] fileNames = {"file.txt", "file^.txt", "file~.txt", "file?.txt", "file@.txt", "file$.txt",
176                                     "file*.txt", "file&.txt", "file#.txt", "file%.txt", "file!.txt"};
177         final FileSystemManager manager = VFS.getManager();
178         final String baseUrl = "zip:file:"+testFile.getAbsolutePath();
179 
180         // test
181         try (FileObject fileObject = manager.resolveFile(baseUrl)) {
182             // test getChildren() number equal
183             assertEquals(fileObject.getChildren().length, fileNames.length);
184 
185             // test getChild(String)
186             for (final String fileName : fileNames) {
187                 assertNotNull(fileObject.getChild(fileName), () -> "can't read file " + fileName);
188             }
189         }
190     }
191 
192     /**
193      * Tests that we can resolve a file in a Zip file, then close the container zip, which should still let us delete
194      * the Zip file.
195      *
196      * @throws IOException
197      */
198     @Test
199     public void testResolveNestedFileWithoutCleanup() throws IOException {
200         final Path newZipFile = createTempFile();
201         final FileSystemManager manager = VFS.getManager();
202         try (FileObject zipFileObject = manager.resolveFile("zip:file:" + newZipFile.toAbsolutePath())) {
203             @SuppressWarnings({ "unused", "resource" })
204             // We resolve a nested file and do nothing else.
205             final FileObject zipFileObject1 = zipFileObject.resolveFile(NESTED_FILE_1);
206         }
207         assertDelete(newZipFile);
208     }
209 
210 }