GordianCoreZipFileContents.java

/*
 * GordianKnot: Security Suite
 * Copyright 2012-2026. Tony Washer
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License.  You may obtain a copy
 * of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package io.github.tonywasher.joceanus.gordianknot.impl.core.zip;

import io.github.tonywasher.joceanus.gordianknot.api.base.GordianException;
import io.github.tonywasher.joceanus.gordianknot.api.zip.GordianZipFileContents;
import io.github.tonywasher.joceanus.gordianknot.api.zip.GordianZipFileEntry;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.zip.ZipEntry;

/**
 * Class represents the contents of an encrypted Zip file.
 */
public class GordianCoreZipFileContents
        implements GordianZipFileContents {
    /**
     * The Header file name.
     */
    protected static final String NAME_HEADER = "jGordianKnotHeader";

    /**
     * The Buffer length.
     */
    private static final int BUFFER_LEN = 100;

    /**
     * The file separator.
     */
    private static final char SEPARATOR_FILE = ';';

    /**
     * Zip File Header.
     */
    private GordianCoreZipFileEntry theHeader;

    /**
     * List of files.
     */
    private final List<GordianZipFileEntry> theList;

    /**
     * Constructor.
     */
    GordianCoreZipFileContents() {
        /* Allocate the list */
        theList = new ArrayList<>();
    }

    /**
     * Constructor from encoded string.
     *
     * @param pCodedString the encoded string
     * @throws GordianException on error
     */
    GordianCoreZipFileContents(final String pCodedString) throws GordianException {
        /* Initialise normally */
        this();

        /* Wrap string in a string builder */
        final StringBuilder myString = new StringBuilder(pCodedString);
        final String myFileSep = Character.toString(SEPARATOR_FILE);

        /* while we have separators in the string */
        while (true) {
            /* Locate End of entry and break loop if not found */
            final int myLoc = myString.indexOf(myFileSep);
            if (myLoc == -1) {
                break;
            }

            /* Parse the encoded entry and remove it from the buffer */
            parseEncodedEntry(myString.substring(0, myLoc));
            myString.delete(0, myLoc + 1);
        }

        /* Parse the remaining entry */
        parseEncodedEntry(myString.toString());
    }

    /**
     * Obtain the header.
     *
     * @return the header
     */
    protected GordianCoreZipFileEntry getHeader() {
        return theHeader;
    }

    @Override
    public Iterator<GordianZipFileEntry> iterator() {
        return theList.iterator();
    }

    /**
     * Add a header entry to the contents.
     *
     * @return the newly added entry
     */
    protected GordianCoreZipFileEntry addZipFileHeader() {
        /* Create the new entry */
        theHeader = new GordianCoreZipFileEntry(NAME_HEADER);
        theHeader.setHeader();
        theHeader.setParent(this);

        /* Return it */
        return theHeader;
    }

    /**
     * Add a File entry to the contents.
     *
     * @param pName the file name
     * @return the newly added entry
     */
    final GordianCoreZipFileEntry addZipFileEntry(final String pName) {
        /* Create the new entry */
        final GordianCoreZipFileEntry myEntry = new GordianCoreZipFileEntry(pName);

        /* Add it to the list */
        addZipFileEntry(myEntry);

        /* Return it */
        return myEntry;
    }

    /**
     * Add a File entry to the contents.
     *
     * @param pEntry the zip entry
     */
    protected final void addZipFileEntry(final ZipEntry pEntry) {
        /* Create the new entry */
        final GordianCoreZipFileEntry myEntry = addZipFileEntry(pEntry.getName());

        /* Record details */
        myEntry.setZipEntry(pEntry);
    }

    /**
     * Add a File entry to the contents.
     *
     * @param pEntry the file entry
     */
    final void addZipFileEntry(final GordianCoreZipFileEntry pEntry) {
        /* Access the name */
        final String myName = pEntry.getFileName();

        /* Loop through the files in the list in the list */
        int iIndex = 0;
        final Iterator<GordianZipFileEntry> myIterator = theList.iterator();
        while (myIterator.hasNext()) {
            /* Access next entry */
            final GordianZipFileEntry myEntry = myIterator.next();

            /* Check the entry name */
            final int iDiff = myName.compareTo(myEntry.getFileName());

            /* If this file is later than us */
            if (iDiff < 0) {
                break;
            }

            /* Reject attempt to add duplicate name */
            if (iDiff == 0) {
                throw new IllegalArgumentException("Duplicate filename - "
                        + myName);
            }

            /* Increment index */
            iIndex++;
        }

        /* Set as child of these contents */
        pEntry.setParent(this);

        /* Add into the list at the correct point */
        theList.add(iIndex, pEntry);
    }

    @Override
    public GordianZipFileEntry findFileEntry(final String pName) {
        /* Loop through the file entries */
        final Iterator<GordianZipFileEntry> myIterator = theList.iterator();
        while (myIterator.hasNext()) {
            /* Access the entry */
            final GordianZipFileEntry myEntry = myIterator.next();

            /* Check the entry name */
            final int iDiff = pName.compareTo(myEntry.getFileName());

            /* If this is the required entry, return it */
            if (iDiff == 0) {
                return myEntry;
            }

            /* If this entry is later than the required name, no such entry */
            if (iDiff < 0) {
                break;
            }
        }

        /* Return not found */
        return null;
    }

    /**
     * Encode the contents.
     *
     * @return the encoded string
     */
    protected String encodeContents() {
        final StringBuilder myString = new StringBuilder(BUFFER_LEN);
        GordianZipFileProperties myProperties;

        /* Loop through the file entries */
        final Iterator<GordianZipFileEntry> myIterator = theList.iterator();
        while (myIterator.hasNext()) {
            /* Access the entry */
            final GordianCoreZipFileEntry myEntry = (GordianCoreZipFileEntry) myIterator.next();

            /* Access the properties */
            myProperties = myEntry.allocateProperties();

            /* Add the value to the string */
            if (myString.length() > 0) {
                myString.append(SEPARATOR_FILE);
            }

            /* Encode the properties */
            myString.append(myProperties.encodeProperties());
        }

        /* Add the value to the string */
        if (myString.length() > 0) {
            myString.append(SEPARATOR_FILE);
        }

        /* Encode the header */
        myProperties = theHeader.allocateProperties();
        myString.append(myProperties.encodeProperties());

        /* Return the encoded string */
        return myString.toString();
    }

    /**
     * Add a File Entry from encoded string.
     *
     * @param pCodedString the encoded string
     * @throws GordianException on error
     */
    private void parseEncodedEntry(final String pCodedString) throws GordianException {
        /* Parse the properties */
        final GordianZipFileProperties myProperties = new GordianZipFileProperties(pCodedString);

        /* Add the zip file entry */
        final GordianCoreZipFileEntry myEntry = new GordianCoreZipFileEntry(myProperties);

        /* If this is a header */
        if (myEntry.isHeader()) {
            /* Store as header */
            theHeader = myEntry;
            theHeader.setParent(this);

            /* else standard file */
        } else {
            /* Add the entry to the list */
            addZipFileEntry(myEntry);
        }
    }
}