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 package org.openoffice.xmerge.converter.xml;
25 
26 import java.util.List;
27 import java.util.ListIterator;
28 import java.util.LinkedList;
29 import java.util.zip.ZipInputStream;
30 import java.util.zip.ZipOutputStream;
31 import java.util.zip.ZipEntry;
32 import java.util.zip.CRC32;
33 import java.io.InputStream;
34 import java.io.OutputStream;
35 import java.io.IOException;
36 import java.io.ByteArrayOutputStream;
37 
38 import org.openoffice.xmerge.util.Debug;
39 
40 /**
41  *  Class used by {@link
42  *  org.openoffice.xmerge.converter.xml.OfficeDocument
43  *  OfficeDocument} to handle reading and writing
44  *  from a ZIP file, as well as storing ZIP entries.
45  *
46  *  @author   Herbie Ong
47  */
48 class OfficeZip {
49 
50     /** File name of the XML file in a zipped document. */
51     private final static String CONTENTXML = "content.xml";
52 
53     private final static String STYLEXML = "styles.xml";
54     private final static String METAXML = "meta.xml";
55     private final static String SETTINGSXML = "settings.xml";
56     private final static String MANIFESTXML = "META-INF/manifest.xml";
57 
58     private final static int BUFFERSIZE = 1024;
59 
60     private List entryList = null;
61 
62     private int contentIndex = -1;
63     private int styleIndex = -1;
64     private int metaIndex = -1;
65     private int settingsIndex = -1;
66     private int manifestIndex = -1;
67 
68     /** Default constructor. */
OfficeZip()69     OfficeZip() {
70 
71         entryList = new LinkedList();
72     }
73 
74 
75     /**
76      *  <p>Read each zip entry in the <code>InputStream</code> object
77      *  and store in entryList both the <code>ZipEntry</code> object
78      *  as well as the bits of each entry.  Call this method before
79      *  calling the <code>getContentXMLBytes</code> method or the
80      *  <code>getStyleXMLBytes</code> method.</p>
81      *
82      *  <p>Keep track of the CONTENTXML and STYLEXML using
83      *  contentIndex and styleIndex, respectively.</p>
84      *
85      *  @param  is  <code>InputStream</code> object to read.
86      *
87      *  @throws  IOException  If any I/O error occurs.
88      */
read(InputStream is)89     void read(InputStream is) throws IOException {
90 
91         ZipInputStream zis = new ZipInputStream(is);
92         ZipEntry ze = null;
93         int i = -1;
94 
95         while ((ze = zis.getNextEntry()) != null) {
96 
97             String name = ze.getName();
98 
99             Debug.log(Debug.TRACE, "reading entry: " + name);
100 
101             ByteArrayOutputStream baos = new ByteArrayOutputStream();
102 
103             int len = 0;
104             byte buffer[] = new byte[BUFFERSIZE];
105 
106             while ((len = zis.read(buffer)) > 0) {
107                 baos.write(buffer, 0, len);
108             }
109 
110             byte bytes[] = baos.toByteArray();
111             Entry entry = new Entry(ze,bytes);
112 
113             entryList.add(entry);
114 
115             i++;
116 
117             if (name.equalsIgnoreCase(CONTENTXML)) {
118                 contentIndex = i;
119             }
120             else if (name.equalsIgnoreCase(STYLEXML)) {
121                 styleIndex = i;
122             }
123             else if (name.equalsIgnoreCase(METAXML)) {
124                 metaIndex = i;
125             }
126             else if (name.equalsIgnoreCase(SETTINGSXML)) {
127                 settingsIndex = i;
128             }
129             else if (name.equalsIgnoreCase(MANIFESTXML)) {
130                 manifestIndex = i;
131             }
132 
133         }
134 
135         zis.close();
136     }
137 
138 
139     /**
140      *  This method returns the CONTENTXML file in a
141      *  <code>byte</code> array.  It returns null if there is no
142      *  CONTENTXML in this zip file.
143      *
144      *  @return  CONTENTXML in a <code>byte</code> array.
145      */
getContentXMLBytes()146     byte[] getContentXMLBytes() {
147 
148         return getEntryBytes(contentIndex);
149     }
150 
151 
152     /**
153      *  This method returns the STYLEXML file in a
154      *  <code>byte</code> array.  It returns null if there is
155      *  no STYLEXML in this zip file.
156      *
157      *  @return  STYLEXML in a <code>byte</code> array.
158      */
getStyleXMLBytes()159     byte[] getStyleXMLBytes() {
160 
161         return getEntryBytes(styleIndex);
162     }
163 
164      /**
165      *  This method returns the METAXML file in a
166      *  <code>byte</code> array.  It returns null if there is
167      *  no METAXML in this zip file.
168      *
169      *  @return  METAXML in a <code>byte</code> array.
170      */
getMetaXMLBytes()171     byte[] getMetaXMLBytes() {
172         return getEntryBytes(metaIndex);
173     }
174 
175       /**
176      *  This method returns the SETTINGSXML file in a
177      *  <code>byte</code> array.  It returns null if there is
178      *  no SETTINGSXML in this zip file.
179      *
180      *  @return  SETTINGSXML in a <code>byte</code> array.
181      */
getSettingsXMLBytes()182     byte[] getSettingsXMLBytes() {
183         return getEntryBytes(settingsIndex);
184     }
185 
186     /**
187      * This method returns the MANIFESTXML file in a <code>byte</code> array.
188      * It returns null if there is no MANIFESTXML in this zip file.
189      *
190      * @return  MANIFESTXML in a <code>byte</code> array.
191      */
getManifestXMLBytes()192     byte[] getManifestXMLBytes() {
193         return getEntryBytes(manifestIndex);
194     }
195 
196     /**
197      * This method returns the bytes corresponding to the entry named in the
198      * parameter.
199      *
200      * @param   name    The name of the entry in the Zip file to retrieve.
201      *
202      * @return  The data for the named entry in a <code>byte</code> array or
203      *          <code>null</code> if no entry is found.
204      */
getNamedBytes(String name)205     byte[] getNamedBytes(String name) {
206 
207         // The list is not sorted, and sorting it for a binary search would
208         // invalidate the indices stored for the main files.
209 
210         // Could improve performance by caching the name and index when
211         // iterating through the ZipFile in read().
212         for (int i = 0; i < entryList.size(); i++) {
213             Entry e = (Entry)entryList.get(i);
214 
215             if (e.zipEntry.getName().equals(name)) {
216                 return getEntryBytes(i);
217             }
218         }
219 
220         return null;
221     }
222 
223 
224     /**
225      * This method sets the bytes for the named entry.  It searches for a
226      * matching entry in the LinkedList.  If no entry is found, a new one is
227      * created.
228      *
229      * Writing of data is defferred to setEntryBytes().
230      *
231      * @param   name    The name of the entry to search for.
232      * @param   bytes   The new data to write.
233      */
setNamedBytes(String name, byte[] bytes)234     void setNamedBytes(String name, byte[] bytes) {
235         for (int i = 0; i < entryList.size(); i++) {
236             Entry e = (Entry)entryList.get(i);
237 
238             if (e.zipEntry.getName().equals(name)) {
239                 setEntryBytes(i, bytes, name);
240                 return;
241             }
242         }
243 
244         // If we're here, no entry was found.  Call setEntryBytes with an index
245         // of -1 to insert a new entry.
246         setEntryBytes(-1, bytes, name);
247     }
248 
249     /**
250      *  Used by the <code>getContentXMLBytes</code> method and the
251      *  <code>getStyleXMLBytes</code> method to return the
252      *  <code>byte</code> array from the corresponding
253      *  <code>entry</code> in the <code>entryList</code>.
254      *
255      *  @param  index  Index of <code>Entry</code> object in
256      *                 <code>entryList</code>.
257      *
258      *  @return  <code>byte</code> array associated in that
259      *           <code>Entry</code> object or null, if there is
260      *           not such <code>Entry</code>.
261      */
getEntryBytes(int index)262     private byte[] getEntryBytes(int index) {
263 
264         byte[] bytes = null;
265 
266         if (index > -1) {
267             Entry entry = (Entry) entryList.get(index);
268             bytes = entry.bytes;
269         }
270         return bytes;
271     }
272 
273 
274     /**
275      *  Set or replace the <code>byte</code> array for the
276      *  CONTENTXML file.
277      *
278      *  @param  bytes  <code>byte</code> array for the
279      *                 CONTENTXML file.
280      */
setContentXMLBytes(byte bytes[])281     void setContentXMLBytes(byte bytes[]) {
282 
283         contentIndex = setEntryBytes(contentIndex, bytes, CONTENTXML);
284     }
285 
286 
287     /**
288      *  Set or replace the <code>byte</code> array for the
289      *  STYLEXML file.
290      *
291      *  @param  bytes  <code>byte</code> array for the
292      *                 STYLEXML file.
293      */
setStyleXMLBytes(byte bytes[])294     void setStyleXMLBytes(byte bytes[]) {
295 
296         styleIndex = setEntryBytes(styleIndex, bytes, STYLEXML);
297     }
298 
299 
300       /**
301      *  Set or replace the <code>byte</code> array for the
302      *  METAXML file.
303      *
304      *  @param  bytes  <code>byte</code> array for the
305      *                 METAXML file.
306      */
setMetaXMLBytes(byte bytes[])307     void setMetaXMLBytes(byte bytes[]) {
308 
309         metaIndex = setEntryBytes(metaIndex, bytes, METAXML);
310     }
311 
312 
313       /**
314      *  Set or replace the <code>byte</code> array for the
315      *  SETTINGSXML file.
316      *
317      *  @param  bytes  <code>byte</code> array for the
318      *                 SETTINGSXML file.
319      */
setSettingsXMLBytes(byte bytes[])320     void setSettingsXMLBytes(byte bytes[]) {
321 
322         settingsIndex = setEntryBytes(settingsIndex, bytes, SETTINGSXML);
323     }
324 
325 
326     /**
327      * Set or replace the <code>byte</code> array for the MANIFESTXML file.
328      *
329      * @param   bytes   <code>byte</code> array for the MANIFESTXML file.
330      */
setManifestXMLBytes(byte bytes[])331     void setManifestXMLBytes(byte bytes[]) {
332         manifestIndex = setEntryBytes(manifestIndex, bytes, MANIFESTXML);
333     }
334 
335     /**
336      *  <p>Used by the <code>setContentXMLBytes</code> method and
337      *  the <code>setStyleXMLBytes</code> to either replace an
338      *  existing <code>Entry</code>, or create a new entry in
339      *  <code>entryList</code>.</p>
340      *
341      *  <p>If there is an <code>Entry</code> object within
342      *  entryList that corresponds to the index, replace the
343      *  <code>ZipEntry</code> info.</p>
344      *
345      *  @param  index  Index of <code>Entry</code> to modify.
346      *  @param  bytes  <code>Entry</code> value.
347      *  @param  name   Name of <code>Entry</code>.
348      *
349      *  @return Index of value added or modified in entryList
350      */
setEntryBytes(int index, byte bytes[], String name)351     private int setEntryBytes(int index, byte bytes[], String name) {
352 
353         if (index > -1) {
354 
355             // replace existing entry in entryList
356 
357             Entry entry = (Entry) entryList.get(index);
358             name = entry.zipEntry.getName();
359             int method = entry.zipEntry.getMethod();
360 
361             ZipEntry ze = createZipEntry(name, bytes, method);
362 
363             entry.zipEntry = ze;
364             entry.bytes= bytes;
365 
366         } else {
367 
368             // add a new entry into entryList
369             ZipEntry ze = createZipEntry(name, bytes, ZipEntry.DEFLATED);
370             Entry entry = new Entry(ze, bytes);
371             entryList.add(entry);
372             index = entryList.size() - 1;
373         }
374 
375         return index;
376     }
377 
378 
379     /**
380      *  Write out the ZIP entries into the <code>OutputStream</code>
381      *  object.
382      *
383      *  @param  os  <code>OutputStream</code> object to write ZIP.
384      *
385      *  @throws  IOException  If any ZIP I/O error occurs.
386      */
write(OutputStream os)387     void write(OutputStream os) throws IOException {
388 
389         Debug.log(Debug.TRACE, "Writing out the following entries into zip.");
390 
391         ZipOutputStream zos = new ZipOutputStream(os);
392 
393         ListIterator iterator = entryList.listIterator();
394 
395         while (iterator.hasNext()) {
396 
397             Entry entry = (Entry) iterator.next();
398             ZipEntry ze = entry.zipEntry;
399 
400             String name = ze.getName();
401 
402             Debug.log(Debug.TRACE, "... " + name);
403 
404             zos.putNextEntry(ze);
405             zos.write(entry.bytes);
406         }
407 
408         zos.close();
409     }
410 
411 
412     /**
413      *  Creates a <code>ZipEntry</code> object based on the given params.
414      *
415      *  @param  name    Name for the <code>ZipEntry</code>.
416      *  @param  bytes   <code>byte</code> array for <code>ZipEntry</code>.
417      *  @param  method  ZIP method to be used for <code>ZipEntry</code>.
418      *
419      *  @return  A <code>ZipEntry</code> object.
420      */
createZipEntry(String name, byte bytes[], int method)421     private ZipEntry createZipEntry(String name, byte bytes[], int method) {
422 
423         ZipEntry ze = new ZipEntry(name);
424 
425         ze.setMethod(method);
426         ze.setSize(bytes.length);
427 
428         CRC32 crc = new CRC32();
429         crc.reset();
430         crc.update(bytes);
431         ze.setCrc(crc.getValue());
432 
433         ze.setTime(System.currentTimeMillis());
434 
435         return ze;
436     }
437 
438     /**
439      *  This inner class is used as a data structure for holding
440      *  a <code>ZipEntry</code> info and its corresponding bytes.
441      *  These are stored in entryList.
442      */
443     private class Entry {
444 
445         ZipEntry zipEntry = null;
446         byte bytes[] = null;
447 
Entry(ZipEntry zipEntry, byte bytes[])448         Entry(ZipEntry zipEntry, byte bytes[]) {
449             this.zipEntry = zipEntry;
450             this.bytes = bytes;
451         }
452     }
453 }
454 
455