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