TdsValidator.java

/*
 * Copyright (C) 2017-2025 Gerd Neugebauer
 *
 * This file is distributed under the 3-clause BSD license.
 * See file LICENSE for details.
 */
package org.ctan.site.services.upload.util;

import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import lombok.extern.slf4j.Slf4j;

/**
 * This class constitutes a validator for TDS archives.
 *
 * @author <a href="gene@ctan.org">Gerd Neugebauer</a>
 */
@Slf4j
public class TdsValidator {

    /**
     * The field <code>TDS</code> contains the specification if the TDS tree.
     */
    protected static final Pattern TDS =
        Pattern.compile(
            "^(bibtex/(bib|bsf|csf)/"
                + "|doc/"
                + "|dvips/"
                + "|fonts/(afm|enc|misc|opentype|ovp|sfd|tfm|type1|"
                + "cmap|map|ofm|pk|source|truetype|vf)/"
                + "|makeindex/"
                + "|metafont/"
                + "|metapost/"
                + "|mft/"
                + "|omega/"
                + "|pbibtex/"
                + "|pbibtex/"
                + "|scripts/"
                + "|source/"
                + "|tex/(amstex|csplain|generic|lualatex|mltex|psizzl|"
                + "texsis|xelatex|context|eplain|lambda|luatex|plain|"
                + "ptex|uplatex|xetex|cslatex|fontinst|latex|mex|"
                + "platex|startex|uptex|xmltex)/"
                + "|texdoc/"
                + "|web2c/).*");

    /**
     * The constant <code>SPECIALS_PATTERN</code> contains the pattern to detect
     * special characters.
     */
    // Pattern.compile(".*([\\x00- "\"`;?\$|(){}\\x7f-\\xff]).*")
    private static final Pattern SPECIALS_PATTERN =
        Pattern.compile(".*([^a-zA-Z0-9_.]).*");

    /**
     * The method <code>check</code> provides means to perform the checks.
     *
     * @param messages the list of messages
     * @param name the name of the file
     * @param pkg the package
     * @param content the content
     */
    public void check(Messages messages, String name, String pkg,
        byte[] content) {

        // TODO unimplemented
        throw new UnsupportedOperationException();
    }

    /**
     * This method inspects the TDS archive and adds messages if something
     * unusual is found.
     *
     * @param messages the messages
     * @param archiveName the name of the archive under inspection
     * @param pkg the name of the package
     * @param zip the content of the TDS archive
     */
    public void check(Messages messages, String archiveName, String pkg,
        ZipInputStream zip) {

        messages.info("TDS archive found", archiveName);

        // if (content.length <= 0) {
        // messages.error("Empty TDS file", archiveName);
        // }

        try {
            // ZipInputStream zip = new ZipFile(new
            // SeekableInMemoryByteChannel(content));
            // try (zip) {
            for (ZipEntry entry = zip.getNextEntry(); entry != null; entry =
                zip.getNextEntry()) {
                String name = entry.getName();

                if (SPECIALS_PATTERN.matcher(name).matches()) {
                    messages.error("Name contains special character in TDS",
                        archiveName,
                        name);
                }
                if (!entry.isDirectory() && !find(name)) {
                    messages.error("Illegal path in TDS", archiveName,
                        name);
                }
                // }
            }
        } catch (Exception e) {
            messages.errorOrWarning("Error reading TDS file", archiveName,
                "File is too highly compressed or no ZIP archive");
            log.error("", e);
        }

    }

    /**
     * This method checks if the given path is allowed according to the TDS
     * specification.
     *
     * @param path the path
     *
     * @return <code>true</code> iff the TDS specification allows the path
     */
    private boolean find(String path) {

        return TDS.matcher(path).matches();
    }
}