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.idesupport.zip;
25 
26 import java.io.*;
27 import java.util.Enumeration;
28 import java.util.zip.*;
29 import java.beans.PropertyVetoException;
30 
31 import org.openoffice.idesupport.filter.FileFilter;
32 import org.openoffice.idesupport.filter.BinaryOnlyFilter;
33 import org.openoffice.idesupport.filter.ExceptParcelFilter;
34 
35 import com.sun.star.script.framework.container.ParcelDescriptor;
36 import org.openoffice.idesupport.xml.Manifest;
37 
38 public class ParcelZipper
39 {
40     public static final String PARCEL_PREFIX_DIR = "Scripts/";
41     public static final String PARCEL_EXTENSION = "sxp";
42     public static final String CONTENTS_DIRNAME = "Contents";
43     public static final String PARCEL_DESCRIPTOR_XML = "parcel-descriptor.xml";
44 
45     private static ParcelZipper zipper = null;
46 
47     private static final FileFilter DEFAULT_FILTER =
48         BinaryOnlyFilter.getInstance();
49 
ParcelZipper()50     private ParcelZipper() {
51     }
52 
getParcelZipper()53     public static ParcelZipper getParcelZipper() {
54         if (zipper == null) {
55             synchronized(ParcelZipper.class) {
56                 if (zipper == null)
57                     zipper = new ParcelZipper();
58             }
59         }
60         return zipper;
61     }
62 
zipParcel(File basedir)63     public String zipParcel(File basedir) throws IOException {
64         File targetfile, targetdir;
65 
66         if (basedir.getName().equals(CONTENTS_DIRNAME))
67             targetdir = basedir.getParentFile();
68         else
69             targetdir = basedir;
70 
71         targetfile = new File(targetdir, targetdir.getName() + "." + PARCEL_EXTENSION);
72 
73         return zipParcel(basedir, targetfile, DEFAULT_FILTER);
74     }
75 
zipParcel(File basedir, File targetfile)76     public String zipParcel(File basedir, File targetfile) throws IOException {
77         return zipParcel(basedir, targetfile, DEFAULT_FILTER);
78     }
79 
zipParcel(File basedir, FileFilter filter)80     public String zipParcel(File basedir, FileFilter filter) throws IOException {
81         File targetfile, targetdir;
82 
83         if (basedir.getName().equals(CONTENTS_DIRNAME))
84             targetdir = basedir.getParentFile();
85         else
86             targetdir = basedir;
87 
88         targetfile = new File(targetdir, targetdir.getName() + "." + PARCEL_EXTENSION);
89 
90         return zipParcel(basedir, targetfile, filter);
91     }
92 
zipParcel(File basedir, File targetfile, FileFilter filter)93     public String zipParcel(File basedir, File targetfile, FileFilter filter)
94         throws IOException {
95         String realpath, tmppath;
96 
97         realpath = targetfile.getPath();
98         tmppath = realpath + ".tmp";
99 
100         File tmpfile = new File(tmppath);
101         ZipOutputStream out = null;
102         try {
103             if (tmpfile.exists() == true)
104                 tmpfile.delete();
105 
106             out = new ZipOutputStream(new FileOutputStream(tmpfile));
107 
108             File[] children = basedir.listFiles();
109             for (int i = 0; i < children.length; i++)
110                 addFileToParcel(children[i], "", out, filter);
111         }
112         catch (IOException ioe) {
113             out.close();
114             tmpfile.delete();
115             throw ioe;
116         }
117         finally {
118             if (out != null)
119                 out.close();
120         }
121 
122         if (targetfile.exists() == true)
123             targetfile.delete();
124         tmpfile.renameTo(targetfile);
125 
126         return targetfile.getAbsolutePath();
127     }
128 
addFileToParcel(File root, String path, ZipOutputStream out, FileFilter filter)129     private void addFileToParcel(File root, String path, ZipOutputStream out, FileFilter filter)
130         throws IOException {
131         ZipEntry ze;
132 
133         if (root.isDirectory() == true) {
134             ze = new ZipEntry(/* PARCEL_PREFIX_DIR + */ path + root.getName() + "/");
135             out.putNextEntry(ze);
136             out.closeEntry();
137             File[] children = root.listFiles();
138 
139             for (int i = 0; i < children.length; i++)
140                 addFileToParcel(children[i], path + root.getName() + "/", out, filter);
141         }
142         else {
143             if (filter.validate(root.getName()) == false &&
144                 root.getName().equals("parcel-descriptor.xml") == false)
145                 return;
146 
147             ze = new ZipEntry(/* PARCEL_PREFIX_DIR + */ path + root.getName());
148             out.putNextEntry(ze);
149 
150             byte[] bytes = new byte[1024];
151             int len;
152 
153             FileInputStream fis = null;
154             try {
155                 fis = new FileInputStream(root);
156 
157                 while ((len = fis.read(bytes)) != -1)
158                     out.write(bytes, 0, len);
159             }
160             finally {
161                 if (fis != null) fis.close();
162             }
163             out.closeEntry();
164         }
165     }
166 
isOverwriteNeeded(File parcel, File target)167     public boolean isOverwriteNeeded(File parcel, File target)
168         throws IOException
169     {
170         boolean result;
171 
172         if (target.isDirectory())
173             result = isDirectoryOverwriteNeeded(parcel, target);
174         else
175             result = isDocumentOverwriteNeeded(parcel, target);
176 
177         return result;
178     }
179 
isDirectoryOverwriteNeeded(File parcel, File target)180     private boolean isDirectoryOverwriteNeeded(File parcel, File target) {
181         String parcelDir = getParcelDirFromParcelZip(parcel.getName());
182 
183         File langdir;
184         try {
185             langdir = new File(target, getParcelLanguage(parcel));
186         }
187         catch (IOException ioe) {
188             return false;
189         }
190 
191         if (langdir.exists() == false)
192             return false;
193 
194         File[] children = langdir.listFiles();
195 
196         for (int i = 0; i < children.length; i++)
197             if (children[i].getName().equals(parcelDir))
198                 return true;
199 
200         return false;
201     }
202 
isDocumentOverwriteNeeded(File parcel, File document)203     private boolean isDocumentOverwriteNeeded(File parcel, File document)
204         throws IOException
205     {
206         ZipFile documentZip = null;
207         boolean result = false;
208 
209         try {
210             documentZip = new ZipFile(document);
211 
212             String name =
213                 PARCEL_PREFIX_DIR + getParcelLanguage(parcel) +
214                     "/" + getParcelDirFromParcelZip(parcel.getName()) +
215                     "/" + PARCEL_DESCRIPTOR_XML;
216 
217             if (documentZip.getEntry(name) != null)
218                 result = true;
219         }
220         catch (IOException ioe) {
221             return false;
222         }
223         finally {
224             if (documentZip != null) documentZip.close();
225         }
226 
227         return result;
228     }
229 
deployParcel(File parcel, File target)230     public String deployParcel(File parcel, File target)
231         throws IOException {
232 
233         String output = null;
234         if (target.isDirectory())
235             output = unzipToDirectory(parcel, target);
236         else
237             output = unzipToZip(parcel, target);
238         return output;
239     }
240 
getParcelDirFromParcelZip(String zipname)241     private String getParcelDirFromParcelZip(String zipname) {
242         String result = zipname.substring(0, zipname.lastIndexOf("."));
243         return result;
244     }
245 
unzipToDirectory(File parcel, File targetDirectory)246     private String unzipToDirectory(File parcel, File targetDirectory)
247         throws IOException {
248 
249         ZipInputStream in = null;
250         File parcelDir = new File(targetDirectory,
251             getParcelLanguage(parcel) + File.separator +
252             getParcelDirFromParcelZip(parcel.getName()));
253 
254         if (isDirectoryOverwriteNeeded(parcel, targetDirectory)) {
255             if (deleteDir(parcelDir) == false) {
256                 throw new IOException("Could not overwrite: " +
257                     parcelDir.getAbsolutePath());
258             }
259         }
260 
261         try {
262             in = new ZipInputStream(new FileInputStream(parcel));
263 
264             File outFile;
265             ZipEntry inEntry = in.getNextEntry();
266             byte[] bytes = new byte[1024];
267             int len;
268 
269             while (inEntry != null) {
270                 outFile = new File(parcelDir, inEntry.getName());
271                 if (inEntry.isDirectory()) {
272                     outFile.mkdir();
273                 }
274                 else {
275                     if (outFile.getParentFile().exists() != true)
276                         outFile.getParentFile().mkdirs();
277 
278                     FileOutputStream out = null;
279                     try {
280                         out = new FileOutputStream(outFile);
281 
282                         while ((len = in.read(bytes)) != -1)
283                             out.write(bytes, 0, len);
284                     }
285                     finally {
286                         if (out != null) out.close();
287                     }
288                 }
289                 inEntry = in.getNextEntry();
290             }
291         }
292         finally {
293             if (in != null) in.close();
294         }
295 
296         return parcelDir.getAbsolutePath();
297     }
298 
deleteDir(File basedir)299     private boolean deleteDir(File basedir) {
300         if (basedir.isDirectory()) {
301             String[] children = basedir.list();
302             for (int i=0; i<children.length; i++) {
303                 boolean success = deleteDir(new File(basedir, children[i]));
304                 if (!success) {
305                     return false;
306                 }
307             }
308         }
309         return basedir.delete();
310     }
311 
unzipToZip(File parcel, File targetDocument)312     private String unzipToZip(File parcel, File targetDocument)
313         throws IOException {
314 
315         ZipInputStream documentStream = null;
316         ZipInputStream parcelStream = null;
317         ZipOutputStream outStream = null;
318         Manifest manifest;
319 
320         String language = getParcelLanguage(parcel);
321 
322         if (isDocumentOverwriteNeeded(parcel, targetDocument)) {
323             String parcelName = language + "/" +
324                 getParcelDirFromParcelZip(parcel.getName());
325             removeParcel(targetDocument, parcelName);
326         }
327 
328         // first write contents of document to tmpfile
329         File tmpfile = new File(targetDocument.getAbsolutePath() + ".tmp");
330 
331         manifest = addParcelToManifest(targetDocument, parcel);
332 
333         try {
334             documentStream =
335                 new ZipInputStream(new FileInputStream(targetDocument));
336             parcelStream = new ZipInputStream(new FileInputStream(parcel));
337             outStream = new ZipOutputStream(new FileOutputStream(tmpfile));
338 
339             copyParcelToZip(parcelStream, outStream, PARCEL_PREFIX_DIR +
340                 language + "/" + getParcelDirFromParcelZip(parcel.getName()));
341             copyDocumentToZip(documentStream, outStream, manifest);
342         }
343         catch (IOException ioe) {
344             tmpfile.delete();
345             throw ioe;
346         }
347         finally {
348             if (documentStream != null) documentStream.close();
349             if (parcelStream != null) parcelStream.close();
350             if (outStream != null) outStream.close();
351         }
352 
353         if (targetDocument.delete() == false) {
354             tmpfile.delete();
355             throw new IOException("Could not overwrite " + targetDocument);
356         }
357         else
358             tmpfile.renameTo(targetDocument);
359 
360         return targetDocument.getAbsolutePath();
361     }
362 
copyParcelToZip(ZipInputStream in, ZipOutputStream out, String parcelName)363     private void copyParcelToZip(ZipInputStream in, ZipOutputStream out,
364         String parcelName) throws IOException {
365 
366         ZipEntry outEntry;
367         ZipEntry inEntry = in.getNextEntry();
368         byte[] bytes = new byte[1024];
369         int len;
370 
371         while (inEntry != null) {
372             if(parcelName!=null)
373                 outEntry = new ZipEntry(parcelName + "/" + inEntry.getName());
374             else
375                 outEntry = new ZipEntry(inEntry);
376             out.putNextEntry(outEntry);
377 
378             if (inEntry.isDirectory() == false)
379                 while ((len = in.read(bytes)) != -1)
380                     out.write(bytes, 0, len);
381 
382             out.closeEntry();
383             inEntry = in.getNextEntry();
384         }
385     }
386 
copyDocumentToZip(ZipInputStream in, ZipOutputStream out, Manifest manifest)387     private void copyDocumentToZip(ZipInputStream in, ZipOutputStream out,
388         Manifest manifest) throws IOException {
389 
390         ZipEntry outEntry;
391         ZipEntry inEntry;
392         byte[] bytes = new byte[1024];
393         int len;
394 
395         while ((inEntry = in.getNextEntry()) != null) {
396 
397             outEntry = new ZipEntry(inEntry);
398             out.putNextEntry(outEntry);
399 
400             if(inEntry.getName().equals("META-INF/manifest.xml") &&
401                manifest != null) {
402                 InputStream manifestStream = null;
403                 try {
404                     manifestStream = manifest.getInputStream();
405                     while ((len = manifestStream.read(bytes)) != -1)
406                         out.write(bytes, 0, len);
407                 }
408                 finally {
409                     if (manifestStream != null)
410                         manifestStream.close();
411                 }
412             }
413             else if (inEntry.isDirectory() == false) {
414                 while ((len = in.read(bytes)) != -1)
415                     out.write(bytes, 0, len);
416             }
417 
418             out.closeEntry();
419         }
420     }
421 
removeParcel(File document, String parcelName)422     public String removeParcel(File document, String parcelName)
423         throws IOException {
424 
425         ZipInputStream documentStream = null;
426         ZipOutputStream outStream = null;
427         Manifest manifest = null;
428 
429         if (!parcelName.startsWith(PARCEL_PREFIX_DIR))
430             parcelName = PARCEL_PREFIX_DIR + parcelName;
431         manifest = removeParcelFromManifest(document, parcelName);
432 
433         // first write contents of document to tmpfile
434         File tmpfile = new File(document.getAbsolutePath() + ".tmp");
435 
436         try {
437             ZipEntry outEntry;
438             ZipEntry inEntry;
439             byte[] bytes = new byte[1024];
440             int len;
441 
442             documentStream = new ZipInputStream(new FileInputStream(document));
443             outStream = new ZipOutputStream(new FileOutputStream(tmpfile));
444 
445             while ((inEntry = documentStream.getNextEntry()) != null) {
446 
447                 if(inEntry.getName().startsWith(parcelName))
448                     continue;
449 
450                 outEntry = new ZipEntry(inEntry);
451                 outStream.putNextEntry(outEntry);
452 
453                 if(inEntry.getName().equals("META-INF/manifest.xml") &&
454                    manifest != null) {
455                     InputStream manifestStream = null;
456                     try {
457                         manifestStream = manifest.getInputStream();
458                         while ((len = manifestStream.read(bytes)) != -1)
459                             outStream.write(bytes, 0, len);
460                     }
461                     finally {
462                         if (manifestStream != null)
463                             manifestStream.close();
464                     }
465                 }
466                 else if (inEntry.isDirectory() == false) {
467                     while ((len = documentStream.read(bytes)) != -1)
468                         outStream.write(bytes, 0, len);
469                 }
470 
471                 outStream.closeEntry();
472             }
473         }
474         catch (IOException ioe) {
475             tmpfile.delete();
476             throw ioe;
477         }
478         finally {
479             if (documentStream != null)
480                 documentStream.close();
481 
482             if (outStream != null)
483                 outStream.close();
484         }
485 
486         if (document.delete() == false) {
487             tmpfile.delete();
488             throw new IOException("Could not overwrite " + document);
489         }
490         else
491             tmpfile.renameTo(document);
492 
493         return document.getAbsolutePath();
494     }
495 
getManifestFromDocument(File document)496     private Manifest getManifestFromDocument(File document) {
497         ZipFile documentZip = null;
498         Manifest result = null;
499 
500         try {
501             documentZip = new ZipFile(document);
502             ZipEntry original = documentZip.getEntry("META-INF/manifest.xml");
503             if (original != null) {
504                 result = new Manifest(documentZip.getInputStream(original));
505             }
506         }
507         catch (IOException ioe) {
508             result = null;
509         }
510         finally {
511             try {
512                 if (documentZip != null)
513                     documentZip.close();
514             }
515             catch (IOException ioe) {}
516         }
517 
518         return result;
519     }
520 
addParcelToManifest(File document, File parcel)521     private Manifest addParcelToManifest(File document, File parcel)
522         throws IOException {
523 
524         ZipFile parcelZip = null;
525         Manifest result = null;
526 
527         result = getManifestFromDocument(document);
528         if (result == null)
529             return null;
530 
531         String language = getParcelLanguage(parcel);
532 
533         try {
534             parcelZip = new ZipFile(parcel);
535 
536             String prefix = PARCEL_PREFIX_DIR + language + "/" +
537                 getParcelDirFromParcelZip(parcel.getName()) + "/";
538 
539             Enumeration entries = parcelZip.entries();
540             while (entries.hasMoreElements()) {
541                 ZipEntry entry = (ZipEntry)entries.nextElement();
542                 result.add(prefix + entry.getName());
543             }
544         }
545         catch (IOException ioe) {
546             return null;
547         }
548         finally {
549             try {
550                 if (parcelZip != null)
551                     parcelZip.close();
552             }
553             catch (IOException ioe) {}
554         }
555 
556         return result;
557     }
558 
removeParcelFromManifest(File document, String name)559     private Manifest removeParcelFromManifest(File document, String name) {
560         Manifest result = null;
561 
562         result = getManifestFromDocument(document);
563         if (result == null)
564             return null;
565 
566         result.remove(name);
567         return result;
568     }
569 
getParcelLanguage(File file)570     public String getParcelLanguage(File file) throws IOException {
571         ZipFile zf = null;
572         ZipEntry ze = null;
573         InputStream is = null;
574         ParcelDescriptor pd;
575 
576         try {
577             zf = new ZipFile(file);
578             ze = zf.getEntry(PARCEL_DESCRIPTOR_XML);
579 
580             if (ze == null)
581                 throw new IOException("Could not find Parcel Descriptor in parcel");
582 
583             is = zf.getInputStream(ze);
584             pd = new ParcelDescriptor(is);
585         }
586         finally {
587             if (zf != null)
588                 zf.close();
589 
590             if (is != null)
591                 is.close();
592         }
593 
594         return pd.getLanguage().toLowerCase();
595     }
596 }
597