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