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