001/*
002 * (C) Copyright 2006-2020 Nuxeo (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Laurent Doguin
018 *     Florent Guillaume
019 */
020package org.nuxeo.ecm.platform.convert.plugins;
021
022import java.io.BufferedInputStream;
023import java.io.File;
024import java.io.IOException;
025import java.io.Serializable;
026import java.util.ArrayList;
027import java.util.List;
028import java.util.Map;
029import java.util.Optional;
030import java.util.zip.ZipEntry;
031import java.util.zip.ZipFile;
032import java.util.zip.ZipInputStream;
033
034import org.apache.commons.text.StringEscapeUtils;
035import org.nuxeo.ecm.core.api.Blob;
036import org.nuxeo.ecm.core.api.Blobs;
037import org.nuxeo.ecm.core.api.NuxeoException;
038import org.nuxeo.ecm.core.api.blobholder.BlobHolder;
039import org.nuxeo.ecm.core.api.blobholder.SimpleBlobHolder;
040import org.nuxeo.ecm.core.api.impl.blob.ZipEntryBlob;
041import org.nuxeo.ecm.core.convert.api.ConversionException;
042import org.nuxeo.ecm.core.convert.extension.Converter;
043import org.nuxeo.ecm.core.convert.extension.ConverterDescriptor;
044
045/**
046 * Zip2Html converter.
047 */
048public class Zip2HtmlConverter implements Converter {
049
050    protected static final String INDEX_HTML = "index.html";
051
052    @Override
053    public BlobHolder convert(BlobHolder blobHolder, Map<String, Serializable> parameters) throws ConversionException {
054        Blob zipBlob = blobHolder.getBlob();
055        String mimeType = zipBlob.getMimeType();
056        if (!mimeType.equals("application/zip") && !mimeType.equals("application/x-zip-compressed")) {
057            throw new ConversionException("not a zip file", blobHolder);
058        }
059
060        List<String> names = new ArrayList<>();
061        Optional<Blob> indexBlob = listNamesAndCreateIndex(zipBlob, names);
062
063        List<Blob> blobs = new ArrayList<>();
064        indexBlob.ifPresent(blobs::add);
065        names.forEach(name -> blobs.add(new ZipEntryBlob(zipBlob, name)));
066        return new SimpleBlobHolder(blobs);
067    }
068
069    protected Optional<Blob> listNamesAndCreateIndex(Blob zipBlob, List<String> names) {
070        File file = zipBlob.getFile();
071        if (file != null) {
072            // if there's a file then we can be fast
073            try (ZipFile zipFile = new ZipFile(file)) {
074                zipFile.stream().forEach(entry -> names.add(entry.getName()));
075            } catch (IOException e) {
076                throw new NuxeoException(e);
077            }
078        } else {
079            // else use the stream
080            try (ZipInputStream zin = new ZipInputStream(new BufferedInputStream(zipBlob.getStream()))) {
081                ZipEntry entry;
082                while ((entry = zin.getNextEntry()) != null) {
083                    names.add(entry.getName());
084                }
085            } catch (IOException e) {
086                throw new NuxeoException(e);
087            }
088        }
089
090        orderIndexFirst(names);
091        if (names.isEmpty() || names.get(0).contains(INDEX_HTML)) {
092            return Optional.empty(); // no index blob to create
093        }
094        Blob indexBlob = createIndexBlob(zipBlob.getFilename(), names);
095        return Optional.of(indexBlob);
096    }
097
098    // see also similar method in SimpleCachableBlobHolder
099    protected void orderIndexFirst(List<String> names) {
100        String indexName = null;
101        for (String name : names) {
102            if (name.contains(INDEX_HTML) && (indexName == null || name.compareTo(indexName) < 0)) {
103                indexName = name;
104            }
105        }
106        if (indexName != null) {
107            names.remove(indexName);
108            names.add(0, indexName);
109        }
110    }
111
112    protected Blob createIndexBlob(String title, List<String> names) {
113        StringBuilder page = new StringBuilder("<html><body>");
114        page.append("<h1>")
115            .append(StringEscapeUtils.escapeEcmaScript(StringEscapeUtils.escapeHtml4(title)))
116            .append("</h1>");
117        page.append("<ul>");
118        for (String name : names) {
119            String fn = StringEscapeUtils.escapeEcmaScript(StringEscapeUtils.escapeHtml4(name));
120            page.append("<li><a href=\"").append(fn).append("\">");
121            page.append(fn);
122            page.append("</a></li>");
123        }
124        page.append("</ul></body></html>");
125        return Blobs.createBlob(page.toString(), "text/html", null, INDEX_HTML);
126    }
127
128    @Override
129    public void init(ConverterDescriptor descriptor) {
130    }
131
132}