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