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