1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package com.xpn.xwiki.plugin.zipexplorer

File ZipExplorerPlugin.java

 

Coverage histogram

../../../../../img/srcFileCovDistChart9.png
38% of files have more coverage

Code metrics

26
90
10
1
350
188
34
0.38
9
10
3.4

Classes

Class Line # Actions
ZipExplorerPlugin 56 90 0% 34 21
0.833333383.3%
 

Contributing tests

This file is covered by 10 tests. .

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package com.xpn.xwiki.plugin.zipexplorer;
21   
22    import java.io.ByteArrayInputStream;
23    import java.io.BufferedInputStream;
24    import java.io.DataInputStream;
25    import java.io.IOException;
26    import java.io.InputStream;
27    import java.net.URLDecoder;
28    import java.util.ArrayList;
29    import java.util.HashMap;
30    import java.util.List;
31    import java.util.Map;
32    import java.util.zip.ZipEntry;
33    import java.util.zip.ZipInputStream;
34   
35    import org.apache.commons.io.IOUtils;
36    import org.slf4j.Logger;
37    import org.slf4j.LoggerFactory;
38   
39    import com.xpn.xwiki.XWikiContext;
40    import com.xpn.xwiki.XWikiException;
41    import com.xpn.xwiki.api.Api;
42    import com.xpn.xwiki.api.Attachment;
43    import com.xpn.xwiki.api.Document;
44    import com.xpn.xwiki.doc.XWikiAttachment;
45    import com.xpn.xwiki.objects.classes.ListItem;
46    import com.xpn.xwiki.plugin.XWikiDefaultPlugin;
47    import com.xpn.xwiki.plugin.XWikiPluginInterface;
48   
49    /**
50    * See {@link com.xpn.xwiki.plugin.zipexplorer.ZipExplorerPluginAPI} for documentation.
51    *
52    * @version $Id: d5323cb04097f78f7f723fbfed39f96d2f78b8f9 $
53    * @deprecated the plugin technology is deprecated, consider rewriting as components
54    */
55    @Deprecated
 
56    public class ZipExplorerPlugin extends XWikiDefaultPlugin
57    {
58    /**
59    * Log object to log messages in this class.
60    */
61    private static final Logger LOG = LoggerFactory.getLogger(ZipExplorerPlugin.class);
62   
63    /**
64    * Path separators for URL.
65    *
66    * @todo Define this somewhere else as this is not specific to this plugin
67    */
68    private static final String URL_SEPARATOR = "/";
69   
70    /**
71    * @param name the plugin name
72    * @param className the plugin classname (used in logs for example)
73    * @param context the XWiki Context
74    *
75    * @see XWikiDefaultPlugin#XWikiDefaultPlugin(String,String,com.xpn.xwiki.XWikiContext)
76    */
 
77  10 toggle public ZipExplorerPlugin(String name, String className, XWikiContext context)
78    {
79  10 super(name, className, context);
80  10 init(context);
81    }
82   
 
83  0 toggle @Override
84    public String getName()
85    {
86  0 return "zipexplorer";
87    }
88   
 
89  0 toggle @Override
90    public Api getPluginApi(XWikiPluginInterface plugin, XWikiContext context)
91    {
92  0 return new ZipExplorerPluginAPI((ZipExplorerPlugin) plugin, context);
93    }
94   
95    /**
96    * For ZIP URLs of the format <code>http://[...]/zipfile.zip/SomeDirectory/SomeFile.txt</code> return a new
97    * attachment containing the file pointed to inside the ZIP. If the original attachment does not point to a ZIP file
98    * or if it doesn't specify a location inside the ZIP then do nothing and return the original attachment.
99    *
100    * @param attachment the original attachment
101    * @param context the XWiki context, used to get the request URL corresponding to the download request
102    * @return a new attachment pointing to the file pointed to by the URL inside the ZIP or the original attachment if
103    * the requested URL doesn't specify a file inside a ZIP
104    * @see com.xpn.xwiki.plugin.XWikiDefaultPlugin#downloadAttachment
105    */
 
106  4 toggle @Override
107    public XWikiAttachment downloadAttachment(XWikiAttachment attachment, XWikiContext context)
108    {
109  4 String url = context.getRequest().getRequestURI();
110   
111    // Verify if we should return the original attachment. We do so when:
112    // * the requested URL doesn't point to a zip file
113    // * or the request URL doesn't point to a file inside a zip file
114    // * or if the passed attachment points to a Nested Space. This is because currently the Zip Explorer plugin
115    // doesn't support Nested Spaces (See http://jira.xwiki.org/browse/XWIKI-12448).
116  4 if (attachment.getReference().getDocumentReference().getSpaceReferences().size() > 1
117    || !isValidZipURL(url, context.getAction().trim()))
118    {
119  3 return attachment;
120    }
121   
122  1 String filename = getFileLocationFromZipURL(url, context.getAction().trim());
123   
124    // Create the new attachment pointing to the file inside the ZIP
125  1 XWikiAttachment newAttachment = new XWikiAttachment();
126  1 newAttachment.setDoc(attachment.getDoc());
127  1 newAttachment.setAuthor(attachment.getAuthor());
128  1 newAttachment.setDate(attachment.getDate());
129   
130  1 InputStream stream = null;
131  1 try {
132  1 stream = new BufferedInputStream(attachment.getContentInputStream(context));
133   
134  1 if (!isZipFile(stream)) {
135  0 return attachment;
136    }
137   
138  1 ZipInputStream zis = new ZipInputStream(stream);
139  1 ZipEntry entry;
140   
141  ? while ((entry = zis.getNextEntry()) != null) {
142  1 String entryName = entry.getName();
143   
144  1 if (entryName.equals(filename)) {
145  1 newAttachment.setFilename(entryName);
146   
147  1 if (entry.getSize() == -1) {
148    // Note: We're copying the content of the file in the ZIP in memory. This is
149    // potentially going to cause an error if the file's size is greater than the
150    // maximum size of a byte[] array in Java or if there's not enough memomry.
151  1 byte[] data = IOUtils.toByteArray(zis);
152   
153  1 newAttachment.setContent(data);
154    } else {
155  0 newAttachment.setContent(zis, (int) entry.getSize());
156    }
157  1 break;
158    }
159    }
160    } catch (XWikiException e) {
161  0 e.printStackTrace();
162    } catch (IOException e) {
163  0 e.printStackTrace();
164    } finally {
165  1 IOUtils.closeQuietly(stream);
166    }
167  1 return newAttachment;
168    }
169   
170    /**
171    * @param document the document containing the ZIP file as an attachment
172    * @param attachmentName the name under which the ZIP file is attached in the document
173    * @param context not used
174    * @return the list of file entries in the ZIP file attached under the passed attachment name inside the passed
175    * document
176    * @see com.xpn.xwiki.plugin.zipexplorer.ZipExplorerPluginAPI#getFileList
177    */
 
178  2 toggle public List<String> getFileList(Document document, String attachmentName, XWikiContext context)
179    {
180  2 List<String> zipList = new ArrayList<String>();
181  2 Attachment attachment = document.getAttachment(attachmentName);
182   
183  2 InputStream stream = null;
184  2 try {
185  2 stream = new ByteArrayInputStream(attachment.getContent());
186   
187  2 if (isZipFile(stream)) {
188  2 ZipInputStream zis = new ZipInputStream(stream);
189  2 ZipEntry entry;
190  ? while ((entry = zis.getNextEntry()) != null) {
191  4 zipList.add(entry.getName());
192    }
193    }
194    } catch (XWikiException e) {
195  0 e.printStackTrace();
196    } catch (IOException e) {
197  0 e.printStackTrace();
198    }
199  2 return zipList;
200    }
201   
202    /**
203    * Finds the ZIP attachment with passed name from the passed document matching and parse the ZIP to generate a list
204    * of {@link com.xpn.xwiki.objects.classes.ListItem} elements representing a tree view of all directories and files
205    * in the ZIP. For example the following zip:
206    * <pre><code>
207    * zipfile.zip:
208    * Directory/File.txt
209    * File2.txt
210    * </code></pre>
211    * generates the following ListItem list:
212    * <pre><code>
213    * { id = "Directory/", value = "Directory", parent = ""}
214    * { id = "Directory/File.txt", value = "File.txt", parent = "Directory/"}
215    * { id = "File2.txt", value = "File2.txt", parent = ""}
216    * </code></pre>
217    *
218    * @param document the document containing the ZIP file as an attachment
219    * @param attachmentName the name under which the ZIP file is attached in the document
220    * @param context not used
221    * @return a tree view list of {@link com.xpn.xwiki.objects.classes.ListItem} elements representing the content of
222    * the ZIP file
223    * @see com.xpn.xwiki.plugin.zipexplorer.ZipExplorerPluginAPI#getFileTreeList
224    */
 
225  1 toggle public List<ListItem> getFileTreeList(Document document, String attachmentName, XWikiContext context)
226    {
227  1 List<String> flatList = getFileList(document, attachmentName, context);
228  1 Map<String, ListItem> fileTree = new HashMap<String, ListItem>();
229  1 List<ListItem> res = new ArrayList<ListItem>();
230  1 for (String url : flatList) {
231  2 StringBuilder buf = new StringBuilder(url.length());
232  2 String parentBuf = "";
233  2 String[] aUrl = url.split(URL_SEPARATOR);
234  5 for (int i = 0; i < aUrl.length; i++) {
235  3 if (i == aUrl.length - 1 && !url.endsWith(URL_SEPARATOR)) {
236  2 buf.append(aUrl[i]);
237    } else {
238  1 buf.append(aUrl[i] + URL_SEPARATOR);
239    }
240  3 ListItem item = new ListItem(buf.toString(), aUrl[i], parentBuf);
241  3 if (!fileTree.containsKey(buf.toString())) {
242  3 res.add(item);
243    }
244  3 fileTree.put(buf.toString(), item);
245  3 parentBuf = buf.toString();
246    }
247    }
248  1 return res;
249    }
250   
251    /**
252    * @param document the document containing the ZIP file as an attachment
253    * @param attachmentName the name under which the ZIP file is attached in the document
254    * @param fileName the filename to concatenate at the end of the attachment URL
255    * @param context not used
256    * @return the attachment URL of the passed attachment located in the passed document to which the passed filename
257    * has been suffixed.
258    * @see com.xpn.xwiki.plugin.zipexplorer.ZipExplorerPluginAPI#getFileLink
259    */
 
260  1 toggle public String getFileLink(Document document, String attachmentName, String fileName, XWikiContext context)
261    {
262  1 return document.getAttachmentURL(attachmentName) + URL_SEPARATOR + fileName;
263    }
264   
265    /**
266    * @param url the URL to parse and from which to extract the relative file location
267    * @param action the XWiki requested action (for example "download", "edit", "view", etc).
268    * @return the relative file location of a file in the ZIP file pointed to by the passed URL. The ZIP URL must be of
269    * the format <code>http://[...]/zipfile.zip/SomeDirectory/SomeFile.txt</code>. With the example above this
270    * method would return <code>SomeDirectory/SomeFile.txt</code>. Return an empty string if the zip URL passed
271    * doesn't point inside a zip file.
272    */
273    // TODO: There should a XWikiURL class possibly extended by a ZipXWikiURL class to handle URL manipulation. Once
274    // this exists remove this code. See http://jira.xwiki.org/jira/browse/XWIKI-437
 
275  14 toggle protected String getFileLocationFromZipURL(String url, String action)
276    {
277  14 String path = url.substring(url.indexOf(URL_SEPARATOR + action));
278  13 int pos = 0;
279  64 for (int i = 0; pos > -1 && i < 4; i++) {
280  51 pos = path.indexOf(URL_SEPARATOR, pos + 1);
281    }
282  13 if (pos == -1) {
283  6 return "";
284    }
285  7 path = path.substring(pos + 1);
286   
287    // Unencode any encoding done by the browser on the URL. For example the browser will
288    // encode spaces and other special characters.
289  7 try {
290  7 path = URLDecoder.decode(path, "UTF-8");
291    } catch (IOException e) {
292    // In case of error we log the error and continue with the undecoded URL.
293    // TODO: Ideally this should rather fail fast but we have no exception handling
294    // framework for scripting code. Change this when we have one.
295  0 LOG.error("Failed to decode URL path [" + path + "]", e);
296    }
297   
298  7 return path;
299    }
300   
301    /**
302    * @param filecontent the content of the file
303    * @return true if the file is in zip format (.zip, .jar etc) or false otherwise
304    */
 
305  6 toggle protected boolean isZipFile(InputStream filecontent)
306    {
307  6 int standardZipHeader = 0x504b0304;
308  6 filecontent.mark(8);
309  6 try {
310  6 DataInputStream datastream = new DataInputStream(filecontent);
311  6 int fileHeader = datastream.readInt();
312  5 return (standardZipHeader == fileHeader);
313    } catch (IOException e) {
314    // The file doesn't have 4 bytes, so it isn't a zip file
315    } finally {
316    // Reset the input stream to the beginning. This may be needed for further reading the archive.
317  6 try {
318  6 filecontent.reset();
319    } catch (IOException e) {
320  0 e.printStackTrace();
321    }
322    }
323  1 return false;
324    }
325   
326    /**
327    * @param url the ZIP URL to check
328    * @param action the XWiki requested action (for example "download", "edit", "view", etc).
329    * @return true if the ZIP URL points to a file inside the ZIP or false otherwise
330    */
 
331  10 toggle protected boolean isValidZipURL(String url, String action)
332    {
333  10 boolean isValidZipURL = false;
334  10 try {
335    // TODO: There shouldn't be the need to do a trim() on an Action. Actually actions
336    // should be enumerated types. See http://jira.xwiki.org/jira/browse/XWIKI-436
337  10 String filenameInZip = getFileLocationFromZipURL(url, action);
338   
339    // TODO: Ideally we should also check to see if the URL points to a file and not to
340    // a directory.
341  9 if (filenameInZip.length() > 0) {
342  4 isValidZipURL = true;
343    }
344    } catch (Exception e) {
345    // TODO: This exception block should be removed and possible errors should be
346    // handled in getFileLocationFromZipURL.
347    }
348  10 return isValidZipURL;
349    }
350    }