All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.zaproxy.zap.extension.script.ScriptsCache Maven / Gradle / Ivy

/*
 * Zed Attack Proxy (ZAP) and its related class files.
 *
 * ZAP is an HTTP/HTTPS proxy for assessing web application security.
 *
 * Copyright 2020 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.zap.extension.script;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;

/**
 * A collection of cached scripts.
 *
 * @since 2.10.0
 * @see ExtensionScript#createScriptsCache(Configuration)
 */
public class ScriptsCache {

    private final ExtensionScript extensionScript;
    private final Configuration config;
    private final InterfaceProvider interfaceProvider;
    private final Map> cache;

    private List> cachedScripts;

    ScriptsCache(ExtensionScript extensionScript, Configuration config) {
        this.extensionScript = extensionScript;
        this.config = config;
        this.interfaceProvider =
                config.getInterfaceProvider() == null
                        ? createDefaultInterfaceProvider()
                        : config.getInterfaceProvider();
        this.cache = Collections.synchronizedMap(new HashMap<>());
        this.cachedScripts = Collections.emptyList();
    }

    private InterfaceProvider createDefaultInterfaceProvider() {
        return (scriptWrapper, targetInterface) -> {
            T script = extensionScript.getInterface(scriptWrapper, targetInterface);
            if (script == null) {
                extensionScript.handleFailedScriptInterface(
                        scriptWrapper,
                        config.getInterfaceErrorMessageProvider().getErrorMessage(scriptWrapper));
            }
            return script;
        };
    }

    /**
     * Refreshes the cache.
     *
     * 

Any scripts that are now disabled are removed, scripts that were changed are recreated. * *

Should be called when the scripts can be safely refreshed, for example, if a script needs * to be initialised before usage the cache should not be refreshed while it's being used. */ public void refresh() { synchronized (cache) { List latestScripts = extensionScript.getScripts(config.getScriptType()); cache.keySet().retainAll(latestScripts); List> latestCachedScripts = new ArrayList<>(); latestScripts.forEach( scriptWrapper -> refreshScriptImpl(scriptWrapper, latestCachedScripts)); cachedScripts = Collections.unmodifiableList(latestCachedScripts); } } private void refreshScriptImpl( ScriptWrapper scriptWrapper, List> latestCachedScripts) { CachedScript cachedScript = cache.computeIfAbsent(scriptWrapper, CachedScript::new); if (!cachedScript.isEnabled()) { cachedScript.setScript(null); return; } if (!cachedScript.hasChanged()) { latestCachedScripts.add(cachedScript); return; } cachedScript.setScript(null); try { T script = interfaceProvider.getInterface(scriptWrapper, config.getTargetInterface()); if (script != null) { cachedScript.setScript(script); latestCachedScripts.add(cachedScript); } } catch (Exception e) { extensionScript.handleScriptException(scriptWrapper, e); } } /** * Executes the given action on all cached scripts. * *

Exceptions thrown during the execution of the action are handled by the {@code * ExtensionScript}. * * @param action the action to be executed on each cached script. * @see ExtensionScript#handleScriptException(ScriptWrapper, Exception) */ public void execute(ScriptAction action) { cachedScripts.forEach( e -> { try { e.execute( () -> { action.apply(e.getScript()); return null; }); } catch (Exception ex) { extensionScript.handleScriptException(e.getScriptWrapper(), ex); } }); } /** * Convenience method that refreshes the cached scripts and executes the given action. * * @param action the action applied to each cached script. * @see #refresh() * @see #execute(ScriptAction) */ public void refreshAndExecute(ScriptAction action) { refresh(); execute(action); } /** * Executes the given action on all cached scripts. * *

Includes the corresponding script wrapper of each script. * *

Exceptions thrown during the execution of the action are handled by the {@code * ExtensionScript}. * * @param action the action to be executed on each cached script. * @see ExtensionScript#handleScriptException(ScriptWrapper, Exception) */ public void execute(ScriptWrapperAction action) { cachedScripts.forEach( e -> { ScriptWrapper sw = e.getScriptWrapper(); try { ExtensionScript.recordScriptCalledStats(sw); e.execute( () -> { action.apply(sw, e.getScript()); return null; }); } catch (Exception ex) { extensionScript.handleScriptException(sw, ex); } }); } /** * Convenience method that refreshes the cached scripts and executes the given action. * * @param action the action applied to each cached script. * @see #refresh() * @see #execute(ScriptWrapperAction) */ public void refreshAndExecute(ScriptWrapperAction action) { refresh(); execute(action); } /** * Gets the cached scripts. * * @return an unmodifiable list containing the cached scripts. */ public List> getCachedScripts() { return cachedScripts; } /** * A cached script, the interface and the corresponding script wrapper. * * @param the type of the interface. */ public static class CachedScript { private final ScriptWrapper scriptWrapper; private int currentModCount; private T script; CachedScript(ScriptWrapper scriptWrapper) { this.scriptWrapper = scriptWrapper; this.currentModCount = scriptWrapper.getModCount(); } /** * Gets the script wrapper. * * @return the script wrapper, never {@code null}. */ public ScriptWrapper getScriptWrapper() { return scriptWrapper; } /** * The script, through the interface. * * @return the script, never {@code null} for users of the collection. */ public T getScript() { return script; } void setScript(T script) { this.script = script; } void execute(Callable action) throws Exception { if (isSyncAccess()) { synchronized (this) { action.call(); } } else { action.call(); } } private boolean isSyncAccess() { ScriptEngineWrapper engine = scriptWrapper.getEngine(); return engine != null && engine.isSingleThreaded(); } boolean hasChanged() { if (script == null) { return true; } int previousModCount = currentModCount; currentModCount = scriptWrapper.getModCount(); return previousModCount != currentModCount; } boolean isEnabled() { return scriptWrapper.isEnabled(); } } /** * The configuration of the {@link ScriptsCache}. * * @param the target type of the scripts. */ public static class Configuration { private final String scriptType; private final Class targetInterface; private final InterfaceProvider interfaceProvider; private final InterfaceErrorMessageProvider interfaceErrorMessageProvider; private Configuration( String scriptType, Class targetInterface, InterfaceProvider interfaceProvider, InterfaceErrorMessageProvider interfaceErrorMessageProvider) { this.scriptType = scriptType; this.targetInterface = targetInterface; this.interfaceProvider = interfaceProvider; this.interfaceErrorMessageProvider = interfaceErrorMessageProvider; } public String getScriptType() { return scriptType; } public Class getTargetInterface() { return targetInterface; } public InterfaceProvider getInterfaceProvider() { return interfaceProvider; } public InterfaceErrorMessageProvider getInterfaceErrorMessageProvider() { return interfaceErrorMessageProvider; } /** * Returns a new configuration builder. * * @param the target type of the scripts. * @return the configuration builder. */ public static Builder builder() { return new Builder<>(); } /** * A builder of configurations. * * @see #build() */ public static class Builder { private String scriptType; private Class targetInterface; private InterfaceProvider interfaceProvider; private InterfaceErrorMessageProvider interfaceErrorMessageProvider; private Builder() {} /** * Sets the script type. * * @param scriptType the script type. * @return this, for chaining. */ public Builder setScriptType(String scriptType) { this.scriptType = scriptType; return this; } /** * Sets the target interface. * * @param targetInterface the target interface. * @return this, for chaining. */ public Builder setTargetInterface(Class targetInterface) { this.targetInterface = targetInterface; return this; } /** * Sets the provider of interfaces. * * @param interfaceProvider the provider of interfaces. * @return this, for chaining. */ public Builder setInterfaceProvider(InterfaceProvider interfaceProvider) { this.interfaceProvider = interfaceProvider; return this; } /** * Sets the provider of error messages. * * @param interfaceErrorMessageProvider the provider of error messages. * @return this, for chaining. */ public Builder setInterfaceErrorMessageProvider( InterfaceErrorMessageProvider interfaceErrorMessageProvider) { this.interfaceErrorMessageProvider = interfaceErrorMessageProvider; return this; } /** * Builds the configuration from the specified data. * * @return the build configuration. * @throws IllegalStateException if the script type or the target interface is not set. * Or, the interface error message provider is set at the same time as the interface * provider. The error message provider is not used when using an interface * provider. */ public final Configuration build() { if (scriptType == null || scriptType.isEmpty()) { throw new IllegalStateException("The script type must be set."); } if (targetInterface == null) { throw new IllegalStateException("The target interface must be set."); } if (interfaceProvider != null && interfaceErrorMessageProvider != null) { throw new IllegalStateException( "The interface error message provider must not be set if using an interface provider."); } return new Configuration<>( scriptType, targetInterface, interfaceProvider, interfaceErrorMessageProvider); } } } /** * An action applied on a script, through the interface. * * @param the type of the interface. */ public interface ScriptAction { /** * Applies the action on the given script. * * @param script the script. * @throws Exception if an error occurred while applying the action to the script. */ void apply(T script) throws Exception; } /** * An action applied on a script, through the interface. * *

For convenience the corresponding wrapper is also provided. * * @param the type of the interface. */ public interface ScriptWrapperAction { /** * Applies the action on the given script. * * @param wrapper the corresponding script wrapper. * @param script the script. * @throws Exception if an error occurred while applying the action to the script. */ void apply(ScriptWrapper wrapper, T script) throws Exception; } /** * A provider of interfaces from scripts. * * @param the type of the interface. */ public interface InterfaceProvider { /** * Gets the given interface from the given script wrapper. * * @param scriptWrapper the script wrapper. * @param targetInterface the target interface. * @return the interface or {@code null} if the script does not implement it. * @throws Exception if an error occurred while creating the interface. */ T getInterface(ScriptWrapper scriptWrapper, Class targetInterface) throws Exception; } /** * A provider of error messages, that indicates that a script wrapper does not implement the * target interface. */ public interface InterfaceErrorMessageProvider { /** * Gets the error message that indicates that the script wrapper does not implement the * interface. * * @param scriptWrapper the script wrapper that does not implement the interface. * @return the error message. */ String getErrorMessage(ScriptWrapper scriptWrapper); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy