Merge branch 'master' of https://source.jalview.org/git/jalviewjs.git
[jalviewjs.git] / src / javajs / util / ZipTools.java
1 /* $RCSfile$
2  * $Author$
3  * $Date$
4  * $Revision$
5  *
6  * Copyright (C) 2006  The Jmol Development Team
7  *
8  * Contact: jmol-developers@lists.sf.net
9  *
10  *  This library is free software; you can redistribute it and/or
11  *  modify it under the terms of the GNU Lesser General Public
12  *  License as published by the Free Software Foundation; either
13  *  version 2.1 of the License, or (at your option) any later version.
14  *
15  *  This library is distributed in the hope that it will be useful,
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  *  Lesser General Public License for more details.
19  *
20  *  You should have received a copy of the GNU Lesser General Public
21  *  License along with this library; if not, write to the Free Software
22  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
23  *  02110-1301, USA.
24  */
25
26 package javajs.util;
27
28 import java.io.BufferedInputStream;
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.io.OutputStream;
32
33 import javajs.J2SIgnoreImport;
34 import javajs.api.GenericZipInputStream;
35 import javajs.api.GenericZipTools;
36 import javajs.api.ZInputStream;
37
38 import java.util.Map;
39 import java.util.zip.CRC32;
40 import java.util.zip.GZIPInputStream;
41 import java.util.zip.ZipEntry;
42 import java.util.zip.ZipInputStream;
43 import java.util.zip.ZipOutputStream;
44
45
46 /**
47  * Note the JSmol/HTML5 must use its own version of java.util.zip.ZipOutputStream.
48  * 
49  */
50 @J2SIgnoreImport({ java.util.zip.ZipOutputStream.class })
51 public class ZipTools implements GenericZipTools {
52
53   public ZipTools() {
54     // for reflection
55   }
56   
57   @Override
58   public ZInputStream newZipInputStream(InputStream is) {
59     return newZIS(is);
60   }
61
62   @SuppressWarnings("resource")
63   private static ZInputStream newZIS(InputStream is) {
64     return (is instanceof ZInputStream ? (ZInputStream) is
65         : is instanceof BufferedInputStream ? new GenericZipInputStream(is)
66             : new GenericZipInputStream(new BufferedInputStream(is)));
67   }
68
69   /**
70    * reads a ZIP file and saves all data in a Hashtable so that the files may be
71    * organized later in a different order. Also adds a #Directory_Listing entry.
72    * 
73    * Files are bracketed by BEGIN Directory Entry and END Directory Entry lines,
74    * similar to CompoundDocument.getAllData.
75    * 
76    * @param is
77    * @param subfileList
78    * @param name0
79    *        prefix for entry listing
80    * @param binaryFileList
81    *        |-separated list of files that should be saved as xx xx xx hex byte
82    *        strings. The directory listing is appended with ":asBinaryString"
83    * @param fileData
84    */
85   @Override
86   public void getAllZipData(InputStream is, String[] subfileList,
87                                           String name0, String binaryFileList,
88                                           Map<String, String> fileData) {
89     ZipInputStream zis = (ZipInputStream) newZIS(is);
90     ZipEntry ze;
91     SB listing = new SB();
92     binaryFileList = "|" + binaryFileList + "|";
93     String prefix = PT.join(subfileList, '/', 1);
94     String prefixd = null;
95     if (prefix != null) {
96       prefixd = prefix.substring(0, prefix.indexOf("/") + 1);
97       if (prefixd.length() == 0)
98         prefixd = null;
99     }
100     try {
101       while ((ze = zis.getNextEntry()) != null) {
102         String name = ze.getName();
103         if (prefix != null && prefixd != null
104             && !(name.equals(prefix) || name.startsWith(prefixd)))
105           continue;
106         //System.out.println("ziputil: " + name);
107         listing.append(name).appendC('\n');
108         String sname = "|" + name.substring(name.lastIndexOf("/") + 1) + "|";
109         boolean asBinaryString = (binaryFileList.indexOf(sname) >= 0);
110         byte[] bytes = Rdr.getLimitedStreamBytes(zis, ze.getSize());
111         String str;
112         if (asBinaryString) {
113           str = getBinaryStringForBytes(bytes);
114           name += ":asBinaryString";
115         } else {
116           str = Rdr.fixUTF(bytes);
117         }
118         str = "BEGIN Directory Entry " + name + "\n" + str
119             + "\nEND Directory Entry " + name + "\n";
120         fileData.put(name0 + "|" + name, str);
121       }
122     } catch (Exception e) {
123     }
124     fileData.put("#Directory_Listing", listing.toString());
125   }
126
127   private String getBinaryStringForBytes(byte[] bytes) {
128     SB ret = new SB();
129     for (int i = 0; i < bytes.length; i++)
130       ret.append(Integer.toHexString(bytes[i] & 0xFF)).appendC(' ');
131     return ret.toString();
132   }
133
134   /**
135    * iteratively drills into zip files of zip files to extract file content or
136    * zip file directory. Also works with JAR files.
137    * 
138    * Does not return "__MACOS" paths
139    * 
140    * @param bis
141    * @param list
142    * @param listPtr
143    * @param asBufferedInputStream
144    *        for Pmesh
145    * @return directory listing or subfile contents
146    */
147   @Override
148   public Object getZipFileDirectory(BufferedInputStream bis, String[] list,
149                                     int listPtr, boolean asBufferedInputStream) {
150     SB ret;
151     if (list == null || listPtr >= list.length)
152       return getZipDirectoryAsStringAndClose(bis);
153     bis = Rdr.getPngZipStream(bis, true);
154     String fileName = list[listPtr];
155     ZipInputStream zis = new ZipInputStream(bis);
156     ZipEntry ze;
157     //System.out.println("fname=" + fileName);
158     try {
159       boolean isAll = (fileName.equals("."));
160       if (isAll || fileName.lastIndexOf("/") == fileName.length() - 1) {
161         ret = new SB();
162         while ((ze = zis.getNextEntry()) != null) {
163           String name = ze.getName();
164           if (isAll || name.startsWith(fileName))
165             ret.append(name).appendC('\n');
166         }
167         String str = ret.toString();
168         return (asBufferedInputStream ? Rdr.getBIS(str.getBytes()) : str);
169       }
170       int pt = fileName.indexOf(":asBinaryString");
171       boolean asBinaryString = (pt > 0);
172       if (asBinaryString)
173         fileName = fileName.substring(0, pt);
174       fileName = fileName.replace('\\', '/');
175       while ((ze = zis.getNextEntry()) != null
176           && !fileName.equals(ze.getName())) {
177       }
178       byte[] bytes = (ze == null ? null : Rdr.getLimitedStreamBytes(zis,
179           ze.getSize()));
180       ze = null;
181       zis.close();
182       if (bytes == null)
183         return "";
184       if (Rdr.isZipB(bytes) || Rdr.isPngZipB(bytes))
185         return getZipFileDirectory(Rdr.getBIS(bytes), list, ++listPtr,
186             asBufferedInputStream);
187       if (asBufferedInputStream)
188         return Rdr.getBIS(bytes);
189       if (asBinaryString) {
190         ret = new SB();
191         for (int i = 0; i < bytes.length; i++)
192           ret.append(Integer.toHexString(bytes[i] & 0xFF)).appendC(' ');
193         return ret.toString();
194       }
195       if (Rdr.isGzipB(bytes))
196         bytes = Rdr.getLimitedStreamBytes(getUnGzippedInputStream(bytes), -1);
197       return Rdr.fixUTF(bytes);
198     } catch (Exception e) {
199       return "";
200     }
201   }
202
203   @Override
204   public byte[] getZipFileContentsAsBytes(BufferedInputStream bis,
205                                           String[] list, int listPtr) {
206     byte[] ret = new byte[0];
207     String fileName = list[listPtr];
208     if (fileName.lastIndexOf("/") == fileName.length() - 1)
209       return ret;
210     try {
211       bis = Rdr.getPngZipStream(bis, true);
212       ZipInputStream zis = new ZipInputStream(bis);
213       ZipEntry ze;
214       while ((ze = zis.getNextEntry()) != null) {
215         if (!fileName.equals(ze.getName()))
216           continue;
217         byte[] bytes = Rdr.getLimitedStreamBytes(zis, ze.getSize());
218         return ((Rdr.isZipB(bytes) || Rdr.isPngZipB(bytes)) && ++listPtr < list.length ? getZipFileContentsAsBytes(
219             Rdr.getBIS(bytes), list, listPtr) : bytes);
220       }
221     } catch (Exception e) {
222     }
223     return ret;
224   }
225   
226   @Override
227   public String getZipDirectoryAsStringAndClose(BufferedInputStream bis) {
228     SB sb = new SB();
229     String[] s = new String[0];
230     try {
231       s = getZipDirectoryOrErrorAndClose(bis, null);
232       bis.close();
233     } catch (Exception e) {
234       System.out.println(e.toString());
235     }
236     for (int i = 0; i < s.length; i++)
237       sb.append(s[i]).appendC('\n');
238     return sb.toString();
239   }
240
241   @Override
242   public String[] getZipDirectoryAndClose(BufferedInputStream bis,
243                                                  String manifestID) {
244     String[] s = new String[0];
245     try {
246       s = getZipDirectoryOrErrorAndClose(bis, manifestID);
247       bis.close();
248     } catch (Exception e) {
249       System.out.println(e.toString());
250     }
251     return s;
252   }
253
254   private String[] getZipDirectoryOrErrorAndClose(BufferedInputStream bis,
255                                                   String manifestID)
256       throws IOException {
257     bis = Rdr.getPngZipStream(bis, true);
258     Lst<String> v = new Lst<String>();
259     ZipInputStream zis = new ZipInputStream(bis);
260     ZipEntry ze;
261     String manifest = null;
262     while ((ze = zis.getNextEntry()) != null) {
263       String fileName = ze.getName();
264       if (manifestID != null && fileName.startsWith(manifestID))
265         manifest = getStreamAsString(zis);
266       else if (!fileName.startsWith("__MACOS")) // resource fork not nec.
267         v.addLast(fileName);
268     }
269     zis.close();
270     if (manifestID != null)
271       v.add(0, manifest == null ? "" : manifest + "\n############\n");
272     return v.toArray(new String[v.size()]);
273   }
274
275   public static String getStreamAsString(InputStream is) throws IOException {
276     return Rdr.fixUTF(Rdr.getLimitedStreamBytes(is, -1));
277   }
278
279   @Override
280   public InputStream newGZIPInputStream(InputStream is) throws IOException {
281     return new BufferedInputStream(new GZIPInputStream(is, 512));
282   }
283
284   @Override
285   public BufferedInputStream getUnGzippedInputStream(byte[] bytes) {
286     try {
287       return Rdr.getUnzippedInputStream(this, Rdr.getBIS(bytes));
288     } catch (Exception e) {
289       return null;
290     }
291   }
292
293   @Override
294   public void addZipEntry(Object zos, String fileName) throws IOException {
295     ((ZipOutputStream) zos).putNextEntry(new ZipEntry(fileName));
296   }
297
298   @Override
299   public void closeZipEntry(Object zos) throws IOException {
300     ((ZipOutputStream) zos).closeEntry();
301   }
302
303   @Override
304   public Object getZipOutputStream(Object bos) {
305     /**
306      * @j2sNative
307      * 
308      *            return javajs.api.Interface.getInterface(
309      *            "java.util.zip.ZipOutputStream").setZOS(bos);
310      * 
311      */
312     {
313       return new ZipOutputStream((OutputStream) bos);
314     }
315   }
316
317   @Override
318   public int getCrcValue(byte[] bytes) {
319     CRC32 crc = new CRC32();
320     crc.update(bytes, 0, bytes.length);
321     return (int) crc.getValue();
322   }
323
324   @Override
325   public void readFileAsMap(BufferedInputStream bis, Map<String, Object> bdata, String name) {
326     int pt = (name == null ? -1 : name.indexOf("|"));
327     name = (pt >= 0 ? name.substring(pt + 1) : null);
328     try {
329       if (Rdr.isPngZipStream(bis)) {
330         boolean isImage = "_IMAGE_".equals(name);
331         if (name == null || isImage)
332           bdata.put((isImage ? "_DATA_" : "_IMAGE_"), new BArray(getPngImageBytes(bis)));
333         if (!isImage)
334           cacheZipContents(bis, name, bdata, true);
335       } else if (Rdr.isZipS(bis)) {
336         cacheZipContents(bis, name, bdata, true);
337       } else if (name == null){
338         bdata.put("_DATA_", new BArray(Rdr.getLimitedStreamBytes(bis, -1)));
339       } else {
340         throw new IOException("ZIP file " + name + " not found");
341       }
342       bdata.put("$_BINARY_$", Boolean.TRUE);
343     } catch (IOException e) {
344       bdata.clear();
345       bdata.put("_ERROR_", e.getMessage());
346     }
347   }
348
349   @Override
350   public String cacheZipContents(BufferedInputStream bis,
351                                         String fileName,
352                                         Map<String, Object> cache, 
353                                         boolean asByteArray) {
354     ZipInputStream zis = (ZipInputStream) newZIS(bis);
355     ZipEntry ze;
356     SB listing = new SB();
357     long n = 0;
358     boolean oneFile = (asByteArray && fileName != null);
359     int pt = (oneFile ? fileName.indexOf("|") : -1);
360     String file0 = (pt >= 0 ? fileName : null);
361     if (pt >= 0)
362       fileName = fileName.substring(0,  pt);
363     try {
364       while ((ze = zis.getNextEntry()) != null) {
365         String name = ze.getName();
366         if (fileName != null) {
367           if (oneFile) {
368             if (!name.equalsIgnoreCase(fileName))
369               continue;
370           } else {
371             listing.append(name).appendC('\n');
372           }
373         }
374         long nBytes = ze.getSize();
375         byte[] bytes = Rdr.getLimitedStreamBytes(zis, nBytes);
376         if (file0 != null) {
377           readFileAsMap(Rdr.getBIS(bytes), cache, file0);
378           return null;
379         }
380         n += bytes.length;
381         Object o = (asByteArray ? new BArray(bytes) : bytes);        
382         cache.put((oneFile ? "_DATA_" : (fileName == null ? "" : fileName + "|") + name), o);
383         if (oneFile)
384           break;
385       }
386       zis.close();
387     } catch (Exception e) {
388       try {
389         zis.close();
390       } catch (IOException e1) {
391       }
392       return null;
393     }
394     if (n == 0 || fileName == null)
395       return null;
396     System.out.println("ZipTools cached " + n + " bytes from " + fileName);
397     return listing.toString();
398   }
399
400   private static byte[] getPngImageBytes(BufferedInputStream bis) {
401     try {
402       if (Rdr.isPngZipStream(bis)) {
403         int pt_count[] = new int[2];
404         Rdr.getPngZipPointAndCount(bis, pt_count);
405         if (pt_count[1] != 0)
406           return deActivatePngZipB(Rdr.getLimitedStreamBytes(bis, pt_count[0]));
407       }
408       return Rdr.getLimitedStreamBytes(bis, -1);
409     } catch (IOException e) {
410       return null;
411     }
412   }
413
414   /**
415    * Once a PNGJ image has been extracted, we want to red-line its
416    * iTXt "Jmol Type PNGJ" tag, since it is no longer associated with
417    * ZIP data.
418    *  
419    * @param bytes
420    * @return disfigured bytes
421    * 
422    */
423   private static byte[] deActivatePngZipB(byte[] bytes) {
424     // \0PNGJ starting at byte 50 changed to \0 NGJ
425     if (Rdr.isPngZipB(bytes))
426       bytes[51] = 32;
427     return bytes;
428   }
429
430
431
432 }