1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 import java.util.List;
25 import java.util.ListIterator;
26 import java.util.LinkedList;
27 import java.util.zip.ZipInputStream;
28 import java.util.zip.ZipOutputStream;
29 import java.util.zip.ZipEntry;
30 import java.util.zip.CRC32;
31 import java.io.InputStream;
32 import java.io.OutputStream;
33 import java.io.IOException;
34 import java.io.ByteArrayOutputStream;
35 import org.openoffice.xmerge.util.Debug;
36 
37 /**
38  *  Class used by OfficeDocument to handle zip reading and writing,
39  *  as well as storing zip entries.
40  *
41  *  @author   Herbie Ong
42  */
43 
44 class OfficeZip {
45 
46     /** file name of the xml file in a zipped document. */
47     private final static String XMLFILE = "content.xml";
48 
49     private final static int BUFFERSIZE = 1024;
50 
51     private List entryList = null;
52 
53     private int contentIndex = -1;
54 
55     private String filename = null;
56 
57     private class Entry {
58 
59         ZipEntry zipEntry = null;
60         byte bytes[] = null;
61     }
62 
63     /**
64      * Constructor
65      *
66      * @param filename Full Path to Zip file to process
67      *
68      */
OfficeZip(String filename)69     public OfficeZip(String filename) {
70         this.filename = filename;
71     }
72 
73 
74     /**
75      *  Read each zip entry in the given InputStream object
76      *  and store in entryList both the ZipEntry object as well
77      *  as the bits of each entry.  Return the bytes for the
78      *  entry of XMLFILE.
79      *
80      *  @param   is   InputStream object to read from
81      *  @return   byte[]   byte array of XML file
82      *  @throws   IOException   if any I/O error occurs
83      */
84 
read(InputStream is)85     byte[] read(InputStream is) throws IOException {
86 
87         ZipInputStream zis = new ZipInputStream(is);
88         ZipEntry ze = null;
89         int i = -1;
90 
91         entryList = new LinkedList();
92 
93         while ((ze = zis.getNextEntry()) != null) {
94 
95             String name = ze.getName();
96 
97             Entry entry = new Entry();
98             entry.zipEntry = ze;
99 
100             Debug.log(Debug.TRACE, "reading entry: " + name);
101 
102             ByteArrayOutputStream baos = new ByteArrayOutputStream();
103 
104             int len = 0;
105             byte bytes[] = new byte[BUFFERSIZE];
106 
107             while ((len = zis.read(bytes)) > 0) {
108                 baos.write(bytes, 0, len);
109             }
110 
111             entry.bytes = baos.toByteArray();
112 
113             entryList.add(entry);
114 
115             i++;
116 
117             if (isContentXML(name)) {
118                 contentIndex = i;
119             }
120         }
121 
122         if (contentIndex == -1) {
123             throw new IOException(XMLFILE + " not found.");
124         }
125 
126         Entry contentEntry = (Entry) entryList.get(contentIndex);
127 
128         return contentEntry.bytes;
129     }
130 
131     /**
132      *  Write out the XMLFILE as a zip into the OutputStream object.
133      *
134      *  If a zip inputstream was previously read, then use
135      *  those zip contents to recreate the zip, except for XMLFILE,
136      *  update it using the new content from xmlBytes.
137      *
138      *  If there was no zip inputstream previously read, write
139      *  XMLFILE out into the zip outputstream.
140      *
141      *  @param   os   OutputStream object to write zip
142      *  @param   xmlBytes   bytes of XMLFILE
143      *  @throws   IOException   if any I/O errors occur.
144      */
145 
write(OutputStream os, byte xmlBytes[])146     void write(OutputStream os, byte xmlBytes[]) throws IOException {
147 
148         ZipOutputStream zos = new ZipOutputStream(os);
149 
150         // if read was not invoked previously, store the bytes directly.
151         if (contentIndex == -1) {
152 
153             Debug.log(Debug.TRACE, "Writing out " + XMLFILE + " into zip.");
154 
155             ZipEntry ze = new ZipEntry(XMLFILE);
156             ze.setSize(xmlBytes.length);
157 
158             CRC32 crc = new CRC32();
159             crc.reset();
160             crc.update(xmlBytes);
161             ze.setCrc(crc.getValue());
162 
163             ze.setTime(System.currentTimeMillis());
164             ze.setMethod(ZipEntry.DEFLATED);
165 
166             zos.putNextEntry(ze);
167             zos.write(xmlBytes);
168 
169         } else {
170 
171             saveEntries(zos, xmlBytes);
172         }
173 
174         zos.close();
175     }
176 
177     /**
178      *  Used by write method if there was a zip inputstream
179      *  previously read.  It would write out each ZipEntry of
180      *  the previously read zip, except for XMLFILE, it would
181      *  update it with new values and with the content from
182      *  xmlBytes.
183      *
184      *  @param   os   OutputStream object to write zip
185      *  @param   xmlBytes   bytes of XMLFILE
186      *  @throws   ZipException   if any zip I/O errors occur.
187      */
188 
saveEntries(ZipOutputStream zos, byte xmlBytes[])189     private void saveEntries(ZipOutputStream zos, byte xmlBytes[])
190         throws IOException {
191 
192         Debug.log(Debug.TRACE, "Writing out the following entries into zip.");
193 
194         ListIterator iterator = entryList.listIterator();
195 
196         while (iterator.hasNext()) {
197 
198             Entry entry = (Entry) iterator.next();
199             ZipEntry ze = entry.zipEntry;
200 
201             String name = ze.getName();
202 
203             Debug.log(Debug.TRACE, "... " + name);
204 
205             if (isContentXML(name)) {
206 
207                 // set new values for this ZipEntry
208 
209                 ZipEntry zipEntry = new ZipEntry(name);
210 
211                 zipEntry.setMethod(ze.getMethod());
212                 zipEntry.setSize(xmlBytes.length);
213 
214                 CRC32 crc = new CRC32();
215                 crc.reset();
216                 crc.update(xmlBytes);
217                 zipEntry.setCrc(crc.getValue());
218 
219                 zipEntry.setTime(System.currentTimeMillis());
220 
221                 zos.putNextEntry(zipEntry);
222                 zos.write(xmlBytes);
223 
224             } else {
225 
226                 zos.putNextEntry(ze);
227                 zos.write(entry.bytes);
228             }
229         }
230     }
231 
isContentXML(String name)232     private boolean isContentXML(String name) {
233 
234         String lname = name.toLowerCase();
235         return lname.equals(XMLFILE);
236     }
237 }
238