org.zaproxy.VerifyScripts.java Source code

Java tutorial

Introduction

Here is the source code for org.zaproxy.VerifyScripts.java

Source

/*
 * Zed Attack Proxy (ZAP) and its related class files.
 *
 * ZAP is an HTTP/HTTPS proxy for assessing web application security.
 *
 * Copyright 2018 The ZAP Development Team
 *
 * 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 org.zaproxy;

import static org.assertj.core.api.Assertions.assertThat;

import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.stream.Stream;
import javax.script.Compilable;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import org.apache.commons.lang3.SystemUtils;
import org.apache.commons.lang3.mutable.MutableInt;
import org.codehaus.groovy.jsr223.GroovyScriptEngineFactory;
import org.jruby.embed.jsr223.JRubyEngineFactory;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.python.core.Options;
import org.python.jsr223.PyScriptEngineFactory;

/** Verifies that the scripts are parsed without errors. */
class VerifyScripts {

    private static final int SCRIPT_TYPE_DIR_DEPTH = 3;

    private static List<Path> files;

    @BeforeAll
    private static void readScriptDirs() throws Exception {
        readFiles();
    }

    @ParameterizedTest(name = "{1}")
    @MethodSource("allScripts")
    void shouldParseScript(Consumer<Reader> parser, @SuppressWarnings("unused") String script, Path path)
            throws Exception {
        try (Reader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
            parser.accept(reader);
        }
    }

    @AfterAll
    private static void verifyAllFilesTested() {
        assertThat(files).as("Not all files were tested: %s", files).isEmpty();
    }

    private static Stream<Arguments> allScripts() {
        return Stream.of(scriptsGroovy(), scriptsJavaScript(), scriptsPython(), scriptsRuby(), scriptsZest())
                .flatMap(s -> s);
    }

    private static Stream<Arguments> scriptsGroovy() {
        return testData(".groovy", (Compilable) new GroovyScriptEngineFactory().getScriptEngine());
    }

    private static Stream<Arguments> scriptsJavaScript() {
        Compilable engine = (Compilable) new ScriptEngineManager().getEngineByName("ECMAScript");
        assertThat(engine).as("ECMAScript script engine exists.").isNotNull();
        return testData(".js", engine);
    }

    private static Stream<Arguments> scriptsPython() {
        Options.importSite = false;
        return testData(".py", (Compilable) new PyScriptEngineFactory().getScriptEngine());
    }

    private static Stream<Arguments> scriptsRuby() {
        if (!SystemUtils.IS_JAVA_1_8) {
            // Ref: https://github.com/zaproxy/zaproxy/issues/3944
            return Stream.empty();
        }
        return testData(".rb", (Compilable) new JRubyEngineFactory().getScriptEngine());
    }

    private static Stream<Arguments> scriptsZest() {
        // Just collect the files for now (Issue 114).
        getFilesWithExtension(".zst");
        return Stream.empty();
    }

    private static Stream<Arguments> testData(String extension, Compilable engine) {
        return testData(extension, parser(engine));
    }

    private static Stream<Arguments> testData(String extension, Consumer<Reader> parser) {
        return getFilesWithExtension(extension).stream()
                .map(e -> Arguments.of(parser, e.getParent().getParent().relativize(e).toString(), e));
    }

    private static Consumer<Reader> parser(Compilable engine) {
        return r -> {
            try {
                engine.compile(r);
            } catch (ScriptException e) {
                throw new RuntimeException(e);
            }
        };
    }

    private static List<Path> getFilesWithExtension(String extension) {
        List<Path> filteredFiles = new ArrayList<>();
        files.removeIf(f -> {
            if (f.getFileName().toString().endsWith(extension)) {
                filteredFiles.add(f);
                return true;
            }
            return false;
        });
        return filteredFiles;
    }

    private static void readFiles() throws Exception {
        Optional<String> path = Arrays.stream(System.getProperty("java.class.path").split(File.pathSeparator))
                .filter(e -> e.endsWith("/scripts")).findFirst();
        assertThat(path).as("The scripts directory was not found on the classpath.").isPresent();

        List<Path> unexpectedFiles = new ArrayList<>();
        MutableInt depth = new MutableInt();

        files = new ArrayList<>();
        Files.walkFileTree(Paths.get(path.get()), new SimpleFileVisitor<Path>() {

            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                depth.increment();
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
                if (depth.intValue() != SCRIPT_TYPE_DIR_DEPTH) {
                    unexpectedFiles.add(file);
                    return FileVisitResult.CONTINUE;
                }

                if (!isExpectedNonScriptFile(file)) {
                    files.add(file);
                }
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
                depth.decrement();
                return FileVisitResult.CONTINUE;
            }
        });

        assertThat(unexpectedFiles).as("Files found not in a script type directory.").isEmpty();

        Collections.sort(files);
    }

    private static boolean isExpectedNonScriptFile(Path file) {
        String fileName = file.getFileName().toString().toLowerCase(Locale.ROOT);
        return fileName.endsWith(".md");
    }
}