VYPR
High severityNVD Advisory· Published Jun 6, 2025· Updated Jun 6, 2025

CVE-2025-5806

CVE-2025-5806

Description

Jenkins Gatling Plugin 136.vb_9009b_3d33a_e serves Gatling reports in a manner that bypasses the Content-Security-Policy protection introduced in Jenkins 1.641 and 1.625, resulting in a cross-site scripting (XSS) vulnerability exploitable by users able to change report content.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Jenkins Gatling Plugin allows XSS by bypassing Content-Security-Policy when serving Gatling reports, enabling attacks from users able to modify report content.

Vulnerability

Overview

Jenkins Gatling Plugin version 136.vb_9009b_3d33a_e serves Gatling performance test reports in a way that bypasses the Content-Security-Policy (CSP) protection that was introduced in Jenkins 1.641 and 1.625 [1]. This CSP bypass creates a cross-site scripting (XSS) vulnerability [2][4].

Attack

Vector

The vulnerability can be exploited by any user who is able to change the content of Gatling reports [1]. Since the plugin renders reports without adhering to the CSP headers that would normally prevent execution of untrusted scripts, an attacker who controls or modifies report data (e.g., by submitting crafted test results) can inject malicious JavaScript into the report pages viewed by other Jenkins users [2].

Impact

Successful exploitation allows an attacker to execute arbitrary JavaScript in the context of a victim's Jenkins session. This could lead to actions such as stealing session cookies, modifying Jenkins configurations, or accessing sensitive information exposed in the Jenkins UI [1][4]. The fix in version 1.3.0 addresses the issue by no longer displaying full Gatling HTML reports directly online; instead, reports must be downloaded locally [3].

Mitigation

Users are strongly advised to upgrade to Gatling Plugin version 1.3.0 or later, which removes the ability to view reports directly within Jenkins and thus closes the CSP bypass vector [3]. No workaround other than upgrading is recommended, as the vulnerability is actively exploitable and has been publicly disclosed [4].

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

2

Patches

1
141bd3a811ab

Merge pull request #27 from vedanthvdev/Security-3588

https://github.com/jenkinsci/gatling-pluginVedanth Vasu DevJun 6, 2025via ghsa
25 files changed · +619 474
  • pom.xml+31 0 modified
    @@ -88,6 +88,13 @@
     			<artifactId>jackson2-api</artifactId>
     		</dependency>
     
    +		<!-- OWASP HTML Sanitizer -->
    +		<dependency>
    +			<groupId>com.googlecode.owasp-java-html-sanitizer</groupId>
    +			<artifactId>owasp-java-html-sanitizer</artifactId>
    +			<version>20211018.2</version>
    +		</dependency>
    +
     		<!-- dependencies on Jenkins Pipeline plugins -->
     		<dependency>
     			<groupId>org.jenkins-ci.plugins.workflow</groupId>
    @@ -113,6 +120,30 @@
     	<build>
     		<pluginManagement>
     			<plugins>
    +				<plugin>
    +					<groupId>com.diffplug.spotless</groupId>
    +					<artifactId>spotless-maven-plugin</artifactId>
    +					<version>2.44.5</version>
    +					<configuration>
    +						<java>
    +							<googleJavaFormat>
    +								<version>1.27.0</version>
    +								<style>GOOGLE</style>
    +							</googleJavaFormat>
    +							<removeUnusedImports/>
    +							<importOrder>
    +								<order>java,javax,org,com,</order>
    +							</importOrder>
    +						</java>
    +					</configuration>
    +					<executions>
    +						<execution>
    +							<goals>
    +								<goal>check</goal>
    +							</goals>
    +						</execution>
    +					</executions>
    +				</plugin>
     				<plugin>
     					<groupId>com.mycila.maven-license-plugin</groupId>
     					<artifactId>maven-license-plugin</artifactId>
    
  • README.md+3 3 modified
    @@ -13,7 +13,7 @@ stress tool, with Jenkins.
     
     This plugin allows you to : 
     
    -- Keep track of a Gatling simulation, providing performance trends across builds
    +- Keep track of a Gatling simulation, providing performance trends across builds(Enterprise Only)
     - Download the detailed reports for each build
     
     ## Changelog
    @@ -115,7 +115,7 @@ The Gatling entry in the left summary has two purposes, depending on
     which page you are.
     
     If you are on the project dashboard, clicking on Gatling will get you to
    -a more detailed performance trend, displaying for your last 30 builds :
    +a more detailed performance trend(Enterprise only), displaying for your last 30 builds :
     
     -   Mean response time trend
     -   95th percentiles response time trend
    @@ -125,7 +125,7 @@ This page will also provides links to download the html reports for all your
     builds, at the bottom of the page.
     
     If you are on the summary of a specific build, clicking on Gatling will
    -get you to a list of all available reports for this build.
    +get you to a list of all available reports for this build without the graph(Enterprise only).
     
     ## Bug Reporting
     
    
  • src/main/java/io/gatling/jenkins/BuildSimulation.java+13 15 modified
    @@ -1,27 +1,25 @@
     /**
      * Copyright 2011-2020 GatlingCorp (http://gatling.io)
      *
    - * 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
    + * <p>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
    + * <p>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
    + * <p>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.gatling.jenkins;
     
    -import hudson.FilePath;
     import java.io.File;
     
    +import hudson.FilePath;
    +
     /**
    - * This class is basically just a struct to hold information about one
    - * or more gatling simulations that were archived for a given
    - * instance of {@link GatlingBuildAction}.
    + * This class is basically just a struct to hold information about one or more gatling simulations
    + * that were archived for a given instance of {@link GatlingBuildAction}.
      */
     public class BuildSimulation {
       private final String simulationName;
    @@ -35,12 +33,12 @@ public BuildSimulation(String simulationName, RequestReport requestReport, File
       }
     
       // see https://wiki.jenkins.io/display/JENKINS/Hint+on+retaining+backward+compatibility
    -  @Deprecated
    -  private transient FilePath simulationDirectory;
    +  @Deprecated private transient FilePath simulationDirectory;
    +
       @SuppressWarnings("unused")
       private Object readResolve() {
         if (simulationDirectory != null) {
    -        simulationPath = new File(simulationDirectory.getRemote());
    +      simulationPath = new File(simulationDirectory.getRemote());
         }
         return this;
       }
    
  • src/main/java/io/gatling/jenkins/chart/Graph.java+15 14 modified
    @@ -1,16 +1,14 @@
     /**
      * Copyright 2011-2020 GatlingCorp (http://gatling.io)
      *
    - * 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
    + * <p>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
    + * <p>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
    + * <p>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.gatling.jenkins.chart;
    @@ -21,12 +19,13 @@
     import java.util.logging.Level;
     import java.util.logging.Logger;
     
    +import com.fasterxml.jackson.databind.ObjectMapper;
    +
     import hudson.model.Job;
     import hudson.model.Run;
    -import io.gatling.jenkins.GatlingBuildAction;
     import io.gatling.jenkins.BuildSimulation;
    +import io.gatling.jenkins.GatlingBuildAction;
     import io.gatling.jenkins.RequestReport;
    -import com.fasterxml.jackson.databind.ObjectMapper;
     
     public abstract class Graph<Y extends Number> {
       private static final Logger LOGGER = Logger.getLogger(Graph.class.getName());
    @@ -43,17 +42,19 @@ public Graph(Job<?, ?> job, int maxBuildsToDisplay) {
           if (action != null) {
             numberOfBuild++;
             for (BuildSimulation sim : action.getSimulations()) {
    -          SerieName serieName = new SerieName(sim.getSimulationName(), sim.getSimulationDirectory().getName());
    +          SerieName serieName =
    +              new SerieName(sim.getSimulationName(), sim.getSimulationDirectory().getName());
               if (!series.containsKey(serieName)) {
                 series.put(serieName, new Serie<>());
               }
     
    -          series.get(serieName).addPoint(run.getNumber(), getValue(sim.getRequestReport()), serieName.getPath());
    +          series
    +              .get(serieName)
    +              .addPoint(run.getNumber(), getValue(sim.getRequestReport()), serieName.getPath());
             }
           }
     
    -      if (numberOfBuild >= maxBuildsToDisplay)
    -        break;
    +      if (numberOfBuild >= maxBuildsToDisplay) break;
         }
       }
     
    
  • src/main/java/io/gatling/jenkins/chart/Point.java+8 10 modified
    @@ -1,16 +1,14 @@
     /**
      * Copyright 2011-2020 GatlingCorp (http://gatling.io)
      *
    - * 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
    + * <p>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
    + * <p>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
    + * <p>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.gatling.jenkins.chart;
    @@ -55,8 +53,8 @@ public void serialize(JsonGenerator jgen, SerializerProvider provider) throws IO
         jgen.writeEndArray();
       }
     
    -  public void serializeWithType(JsonGenerator jgen, SerializerProvider provider, TypeSerializer typeSer) {
    +  public void serializeWithType(
    +      JsonGenerator jgen, SerializerProvider provider, TypeSerializer typeSer) {
         throw new UnsupportedOperationException();
       }
    -
     }
    
  • src/main/java/io/gatling/jenkins/chart/Serie.java+9 10 modified
    @@ -1,16 +1,14 @@
     /**
      * Copyright 2011-2020 GatlingCorp (http://gatling.io)
      *
    - * 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
    + * <p>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
    + * <p>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
    + * <p>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.gatling.jenkins.chart;
    @@ -33,12 +31,13 @@ public void addPoint(X x, Y y, String name) {
       }
     
       public void serialize(JsonGenerator jgen, SerializerProvider provider) throws IOException {
    -    List<Point<X,Y>> reversePoints = points.subList(0, points.size());
    +    List<Point<X, Y>> reversePoints = points.subList(0, points.size());
         Collections.reverse(reversePoints);
         jgen.writeObject(reversePoints);
       }
     
    -  public void serializeWithType(JsonGenerator jgen, SerializerProvider provider, TypeSerializer typeSer) {
    +  public void serializeWithType(
    +      JsonGenerator jgen, SerializerProvider provider, TypeSerializer typeSer) {
         throw new UnsupportedOperationException();
       }
     }
    
  • src/main/java/io/gatling/jenkins/chart/SerieName.java+8 9 modified
    @@ -1,16 +1,14 @@
     /**
      * Copyright 2011-2020 GatlingCorp (http://gatling.io)
      *
    - * 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
    + * <p>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
    + * <p>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
    + * <p>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.gatling.jenkins.chart;
    @@ -40,7 +38,8 @@ public void serialize(JsonGenerator jgen, SerializerProvider provider) throws IO
         jgen.writeEndObject();
       }
     
    -  public void serializeWithType(JsonGenerator jgen, SerializerProvider provider, TypeSerializer typeSer) {
    +  public void serializeWithType(
    +      JsonGenerator jgen, SerializerProvider provider, TypeSerializer typeSer) {
         throw new UnsupportedOperationException();
       }
     
    
  • src/main/java/io/gatling/jenkins/GatlingBuildAction.java+33 25 modified
    @@ -1,43 +1,40 @@
     /**
      * Copyright 2011-2020 GatlingCorp (http://gatling.io)
      *
    - * 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
    + * <p>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
    + * <p>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
    + * <p>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.gatling.jenkins;
     
     import static io.gatling.jenkins.PluginConstants.*;
     
    -import hudson.model.Action;
    -import hudson.model.Run;
    -import jenkins.tasks.SimpleBuildStep;
    -
     import java.util.ArrayList;
     import java.util.Collection;
     import java.util.List;
     
    +import hudson.model.Action;
    +import hudson.model.Run;
    +import jenkins.tasks.SimpleBuildStep;
    +
     /**
      * An Action to add to a project/job's build/run page in the UI.
      *
    - * Note that in order to be compatible with the new Jenkins Pipeline jobs,
    - * Actions that should be added to the project/job's page directly must be added
    - * by implementing the SimpleBuildStep.LastBuildAction interface, and encapsulating
    - * the project actions in the build action via the getProjectActions method.
    + * <p>Note that in order to be compatible with the new Jenkins Pipeline jobs, Actions that should be
    + * added to the project/job's page directly must be added by implementing the
    + * SimpleBuildStep.LastBuildAction interface, and encapsulating the project actions in the build
    + * action via the getProjectActions method.
      *
    - * This is necessary and now preferred to the old approach of defining getProjectAction
    - * directly on the Publisher, because for a Pipeline job, Jenkins doesn't know ahead
    - * of time what actions will be triggered, and will never call the Publisher.getProjectAction
    - * method.  Attaching it as a LastBuildAction means that it is discoverable once
    - * the Pipeline job has been run once.
    + * <p>This is necessary and now preferred to the old approach of defining getProjectAction directly
    + * on the Publisher, because for a Pipeline job, Jenkins doesn't know ahead of time what actions
    + * will be triggered, and will never call the Publisher.getProjectAction method. Attaching it as a
    + * LastBuildAction means that it is discoverable once the Pipeline job has been run once.
      */
     public class GatlingBuildAction implements Action, SimpleBuildStep.LastBuildAction {
     
    @@ -70,11 +67,11 @@ public String getUrlName() {
       }
     
       /**
    -   * This method is called dynamically for any HTTP request to our plugin's
    -   * URL followed by "/report/reportName".
    +   * This method is called dynamically for any HTTP request to our plugin's URL followed by
    +   * "/report/reportName".
        *
    -   * It returns a new instance of {@link ReportViewer}, which contains the
    -   * actual logic for downloading a report.
    +   * <p>It returns a new instance of {@link ReportViewer}, which contains the actual logic for
    +   * downloading a report.
        *
        * @param reportName the name of the reportName
        */
    @@ -84,11 +81,22 @@ public ReportViewer getReport(String reportName) {
         return simulation != null ? new ReportViewer(simulation) : null;
       }
     
    +  @SuppressWarnings("unused")
    +  public ReportDownloader getDownload(String reportName) {
    +    BuildSimulation simulation = getSimulationByReportName(reportName);
    +    return simulation != null ? new ReportDownloader(simulation) : null;
    +  }
    +
       @SuppressWarnings("unused")
       public String getReportURL(BuildSimulation simulation) {
         return URL_NAME + "/report/" + simulation.getSimulationDirectory().getName();
       }
     
    +  @SuppressWarnings("unused")
    +  public String getDownloadURL(BuildSimulation simulation) {
    +    return URL_NAME + "/download/" + simulation.getSimulationDirectory().getName();
    +  }
    +
       private BuildSimulation getSimulationByReportName(String reportName) {
         for (BuildSimulation sim : this.getSimulations()) {
           if (sim.getSimulationDirectory().getName().equals(reportName)) {
    
  • src/main/java/io/gatling/jenkins/GatlingProjectAction.java+13 13 modified
    @@ -1,29 +1,26 @@
     /**
      * Copyright 2011-2020 GatlingCorp (http://gatling.io)
      *
    - * 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
    + * <p>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
    + * <p>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
    + * <p>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.gatling.jenkins;
     
     import static io.gatling.jenkins.PluginConstants.*;
     
    -import hudson.model.*;
    -
    +import java.util.ArrayList;
     import java.util.LinkedHashMap;
    -import java.util.Map;
     import java.util.List;
    -import java.util.ArrayList;
    +import java.util.Map;
     
    +import hudson.model.*;
     import io.gatling.jenkins.chart.Graph;
     
     public class GatlingProjectAction implements Action {
    @@ -96,7 +93,10 @@ public Graph<Long> getRequestKOPercentageGraph() {
         return new Graph<Long>(job, MAX_BUILDS_TO_DISPLAY) {
           @Override
           public Long getValue(RequestReport requestReport) {
    -        return Math.round(requestReport.getNumberOfRequests().getKO() * 100.0 / requestReport.getNumberOfRequests().getTotal());
    +        return Math.round(
    +            requestReport.getNumberOfRequests().getKO()
    +                * 100.0
    +                / requestReport.getNumberOfRequests().getTotal());
           }
         };
       }
    
  • src/main/java/io/gatling/jenkins/GatlingPublisher.java+54 35 modified
    @@ -1,41 +1,41 @@
     /**
      * Copyright 2011-2020 GatlingCorp (http://gatling.io)
      *
    - * 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
    + * <p>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
    + * <p>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
    + * <p>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.gatling.jenkins;
     
    +import java.io.File;
    +import java.io.IOException;
    +import java.io.PrintStream;
    +import java.util.ArrayList;
    +import java.util.Collections;
    +import java.util.List;
    +
    +import javax.annotation.Nonnull;
    +
    +import org.kohsuke.stapler.DataBoundConstructor;
    +
    +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
     import hudson.Extension;
     import hudson.FilePath;
     import hudson.Launcher;
    -import hudson.init.Initializer;
     import hudson.init.InitMilestone;
    +import hudson.init.Initializer;
     import hudson.model.*;
     import hudson.tasks.BuildStepDescriptor;
     import hudson.tasks.BuildStepMonitor;
     import hudson.tasks.Publisher;
     import hudson.tasks.Recorder;
     import jenkins.tasks.SimpleBuildStep;
    -import org.kohsuke.stapler.DataBoundConstructor;
    -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
    -
    -import javax.annotation.Nonnull;
    -import java.io.File;
    -import java.io.IOException;
    -import java.io.PrintStream;
    -import java.util.ArrayList;
    -import java.util.Collections;
    -import java.util.List;
     
     public class GatlingPublisher extends Recorder implements SimpleBuildStep {
     
    @@ -46,12 +46,14 @@ public GatlingPublisher(Boolean enabled) {
         this.enabled = enabled;
       }
     
    -
       @Override
    -  public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
    +  public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener)
    +      throws InterruptedException, IOException {
         FilePath workspace = build.getWorkspace();
         if (workspace == null) {
    -      listener.getLogger().println("Failed to access workspace, it may be on a non-connected slave.");
    +      listener
    +          .getLogger()
    +          .println("Failed to access workspace, it may be on a non-connected slave.");
           return false;
         }
     
    @@ -60,12 +62,17 @@ public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListen
       }
     
       @Override
    -  public void perform(@Nonnull Run<?, ?> run, @Nonnull FilePath workspace, @Nonnull Launcher launcher, @Nonnull TaskListener listener)
    +  public void perform(
    +      @Nonnull Run<?, ?> run,
    +      @Nonnull FilePath workspace,
    +      @Nonnull Launcher launcher,
    +      @Nonnull TaskListener listener)
           throws InterruptedException, IOException {
         PrintStream logger = listener.getLogger();
         if (enabled == null) {
           logger.println("Cannot check Gatling simulation tracking status, reports won't be archived.");
    -      logger.println("Please make sure simulation tracking is enabled in your build configuration !");
    +      logger.println(
    +          "Please make sure simulation tracking is enabled in your build configuration !");
           return;
         }
         if (!enabled) {
    @@ -104,8 +111,12 @@ public BuildStepMonitor getRequiredMonitorService() {
         return BuildStepMonitor.BUILD;
       }
     
    -  @SuppressFBWarnings(value="NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE")
    -  private List<BuildSimulation> saveFullReports(@Nonnull Run<?,?> run, @Nonnull FilePath workspace, @Nonnull File rootDir, @Nonnull PrintStream logger)
    +  @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE")
    +  private List<BuildSimulation> saveFullReports(
    +      @Nonnull Run<?, ?> run,
    +      @Nonnull FilePath workspace,
    +      @Nonnull File rootDir,
    +      @Nonnull PrintStream logger)
           throws IOException, InterruptedException {
         logger.println("here is the workspace: " + workspace);
         FilePath[] files = workspace.list("**/index.html");
    @@ -135,7 +146,8 @@ private List<BuildSimulation> saveFullReports(@Nonnull Run<?,?> run, @Nonnull Fi
         logger.println("Simulations directory: " + allSimulationsDirectory);
     
         if (!allSimulationsDirectory.exists() && !allSimulationsDirectory.mkdirs()) {
    -      logger.println("Could not create simulations archive directory '" + allSimulationsDirectory + "'");
    +      logger.println(
    +          "Could not create simulations archive directory '" + allSimulationsDirectory + "'");
           return Collections.emptyList();
         }
     
    @@ -146,12 +158,14 @@ private List<BuildSimulation> saveFullReports(@Nonnull Run<?,?> run, @Nonnull Fi
           File simulationDirectory = new File(allSimulationsDirectory, name);
     
           if (simulationDirectory.exists()) {
    -        logger.printf("Simulation archive directory '%s' already exists, skipping.%n", simulationDirectory);
    +        logger.printf(
    +            "Simulation archive directory '%s' already exists, skipping.%n", simulationDirectory);
             continue;
           }
     
           if (!simulationDirectory.mkdirs()) {
    -        logger.printf("Could not create simulation archive directory '%s', skipping.%n", simulationDirectory);
    +        logger.printf(
    +            "Could not create simulation archive directory '%s', skipping.%n", simulationDirectory);
             continue;
           }
     
    @@ -161,20 +175,23 @@ private List<BuildSimulation> saveFullReports(@Nonnull Run<?,?> run, @Nonnull Fi
           try {
             SimulationReport report = new SimulationReport(reportDirectory, simulation);
             report.readStatsFile();
    -        BuildSimulation sim = new BuildSimulation(simulation, report.getGlobalReport(), simulationDirectory);
    +        BuildSimulation sim =
    +            new BuildSimulation(simulation, report.getGlobalReport(), simulationDirectory);
             simsToArchive.add(sim);
             logger.println("Successfully archived report for simulation: " + simulation);
           } catch (Exception e) {
    -        logger.println("Error processing report for simulation " + simulation + ": " + e.getMessage());
    +        logger.println(
    +            "Error processing report for simulation " + simulation + ": " + e.getMessage());
           }
         }
     
         return simsToArchive;
       }
     
       @Nonnull
    -  private static List<FilePath> selectReports(@Nonnull Run<?, ?> run, @Nonnull List<FilePath> reportFolders,
    -                                              @Nonnull PrintStream logger) throws InterruptedException, IOException {
    +  private static List<FilePath> selectReports(
    +      @Nonnull Run<?, ?> run, @Nonnull List<FilePath> reportFolders, @Nonnull PrintStream logger)
    +      throws InterruptedException, IOException {
         long buildStartTime = run.getStartTimeInMillis();
         List<FilePath> reportsFromThisBuild = new ArrayList<>();
         for (FilePath reportFolder : reportFolders) {
    @@ -209,8 +226,10 @@ public String getDisplayName() {
         @Initializer(before = InitMilestone.PLUGINS_STARTED)
         @SuppressWarnings("unused")
         public static void addAliases() {
    -      Items.XSTREAM2.addCompatibilityAlias("io.gatling.jenkins.GatlingPublisher", GatlingPublisher.class);
    -      Items.XSTREAM2.addCompatibilityAlias("io.gatling.jenkins.GatlingBuildAction", GatlingBuildAction.class);
    +      Items.XSTREAM2.addCompatibilityAlias(
    +          "io.gatling.jenkins.GatlingPublisher", GatlingPublisher.class);
    +      Items.XSTREAM2.addCompatibilityAlias(
    +          "io.gatling.jenkins.GatlingBuildAction", GatlingBuildAction.class);
         }
       }
     }
    
  • src/main/java/io/gatling/jenkins/PluginConstants.java+6 8 modified
    @@ -1,16 +1,14 @@
     /**
      * Copyright 2011-2020 GatlingCorp (http://gatling.io)
      *
    - * 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
    + * <p>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
    + * <p>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
    + * <p>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.gatling.jenkins;
    
  • src/main/java/io/gatling/jenkins/ReportDownloader.java+69 36 modified
    @@ -1,58 +1,91 @@
     /**
      * Copyright 2011-2020 GatlingCorp (http://gatling.io)
      *
    - * 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
    + * <p>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
    + * <p>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
    + * <p>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.gatling.jenkins;
     
    -import hudson.util.IOUtils;
    -import javax.servlet.ServletOutputStream;
    +import java.io.File;
    +import java.io.IOException;
    +import java.nio.file.Files;
    +import java.util.zip.ZipEntry;
    +import java.util.zip.ZipOutputStream;
    +
    +import javax.servlet.ServletException;
    +
     import org.kohsuke.stapler.StaplerRequest;
     import org.kohsuke.stapler.StaplerResponse;
     
    -import java.io.File;
    -import java.io.IOException;
    +import hudson.model.Action;
     
    -/**
    - * This class is used to download the zipped Reports file
    - */
    -public class ReportDownloader {
    +/** This class is used to download the zipped Reports file */
    +public class ReportDownloader implements Action {
     
    -  private BuildSimulation simulation;
    +  private final BuildSimulation simulation;
     
       public ReportDownloader(BuildSimulation simulation) {
         this.simulation = simulation;
       }
     
    -  /**
    -   * This method will be called when the user clicks on the Gatling reports link
    -   *
    -   * @param request
    -   * @param response
    -   * @throws IOException
    -   * @throws InterruptedException
    -   */
    -  @SuppressWarnings("unused")
    -  public void doIndex(StaplerRequest request, StaplerResponse response)
    -          throws IOException, InterruptedException {
    -    try (ServletOutputStream os = response.getOutputStream()) {
    -      File file = ZipSimulationUtil.getSimulationZip(simulation.getSimulationDirectory());
    -
    -      response.setContentType("application/zip");
    -      response.setContentLength((int)file.length());
    -      response.addHeader("Content-Disposition","attachment;filename=\"" + simulation.getSimulationName()  + ".zip\"");
    -
    -      IOUtils.copy(file, os);
    +  @Override
    +  public String getIconFileName() {
    +    return null;
    +  }
    +
    +  @Override
    +  public String getDisplayName() {
    +    return "Download Gatling Report";
    +  }
    +
    +  @Override
    +  public String getUrlName() {
    +    return "download";
    +  }
    +
    +  public void doDynamic(StaplerRequest req, StaplerResponse rsp)
    +      throws IOException, ServletException {
    +    File reportDir = simulation.getSimulationDirectory();
    +    if (!reportDir.exists() || !reportDir.isDirectory()) {
    +      rsp.sendError(404, "Report not found");
    +      return;
    +    }
    +
    +    // Set headers for zip download
    +    rsp.setContentType("application/zip");
    +    rsp.setHeader("Content-Disposition", "attachment; filename=" + reportDir.getName() + ".zip");
    +
    +    // Create zip file
    +    try (ZipOutputStream zos = new ZipOutputStream(rsp.getOutputStream())) {
    +      addToZip(reportDir, reportDir.getName(), zos);
    +    }
    +  }
    +
    +  private void addToZip(File file, String entryName, ZipOutputStream zos) throws IOException {
    +    if (file.isDirectory()) {
    +      // Add directory entry
    +      zos.putNextEntry(new ZipEntry(entryName + "/"));
    +      zos.closeEntry();
    +
    +      // Add all files in directory
    +      File[] files = file.listFiles();
    +      if (files != null) {
    +        for (File f : files) {
    +          addToZip(f, entryName + "/" + f.getName(), zos);
    +        }
    +      }
    +    } else {
    +      // Add file entry
    +      zos.putNextEntry(new ZipEntry(entryName));
    +      Files.copy(file.toPath(), zos);
    +      zos.closeEntry();
         }
       }
     }
    
  • src/main/java/io/gatling/jenkins/ReportViewer.java+121 72 modified
    @@ -1,93 +1,142 @@
     package io.gatling.jenkins;
     
    -import hudson.model.Action;
    -import org.kohsuke.stapler.StaplerRequest;
    -import org.kohsuke.stapler.StaplerResponse;
    -
    -import javax.servlet.ServletException;
     import java.io.File;
     import java.io.FileInputStream;
     import java.io.IOException;
     import java.io.InputStream;
     import java.io.OutputStream;
    +import java.nio.charset.StandardCharsets;
    +import java.nio.file.Files;
     
    -public class ReportViewer implements Action {
    -    private final BuildSimulation simulation;
    +import javax.servlet.ServletException;
     
    -    public ReportViewer(BuildSimulation simulation) {
    -        this.simulation = simulation;
    -    }
    +import org.kohsuke.stapler.StaplerRequest;
    +import org.kohsuke.stapler.StaplerResponse;
     
    -    @Override
    -    public String getIconFileName() {
    -        return null;
    -    }
    +import hudson.model.Action;
     
    -    @Override
    -    public String getDisplayName() {
    -        return "Gatling Report";
    +public class ReportViewer implements Action {
    +  private final BuildSimulation simulation;
    +
    +  public ReportViewer(BuildSimulation simulation) {
    +    this.simulation = simulation;
    +  }
    +
    +  @Override
    +  public String getIconFileName() {
    +    return null;
    +  }
    +
    +  @Override
    +  public String getDisplayName() {
    +    return "Gatling Report";
    +  }
    +
    +  @Override
    +  public String getUrlName() {
    +    return "report";
    +  }
    +
    +  public void doIndex(StaplerRequest req, StaplerResponse rsp)
    +      throws IOException, ServletException {
    +    File reportDir = simulation.getSimulationDirectory();
    +    File indexFile = new File(reportDir, "index.html");
    +
    +    if (!indexFile.exists()) {
    +      rsp.sendError(404, "Report not found");
    +      return;
         }
     
    -    @Override
    -    public String getUrlName() {
    -        return "report";
    +    // Read the HTML content
    +    String htmlContent = new String(Files.readAllBytes(indexFile.toPath()), StandardCharsets.UTF_8);
    +
    +    // Remove any script tags and their contents
    +    htmlContent = htmlContent.replaceAll("<script[^>]*>.*?</script>", "");
    +    // Remove any event handlers
    +    htmlContent = htmlContent.replaceAll("\\s+on\\w+\\s*=\\s*\"[^\"]*\"", "");
    +    htmlContent = htmlContent.replaceAll("\\s+on\\w+\\s*=\\s*'[^']*'", "");
    +    // Remove any javascript: URLs
    +    htmlContent = htmlContent.replaceAll("javascript:[^\"']*", "");
    +
    +    // Set security headers
    +    rsp.setHeader(
    +        "Content-Security-Policy",
    +        "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;");
    +    rsp.setHeader("X-Content-Type-Options", "nosniff");
    +    rsp.setHeader("X-Frame-Options", "SAMEORIGIN");
    +    rsp.setContentType("text/html;charset=UTF-8");
    +
    +    try (OutputStream out = rsp.getOutputStream()) {
    +      out.write(htmlContent.getBytes(StandardCharsets.UTF_8));
         }
    +  }
     
    -    public void doIndex(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
    -        File reportDir = simulation.getSimulationDirectory();
    -        File indexFile = new File(reportDir, "index.html");
    -
    -        if (!indexFile.exists()) {
    -            rsp.sendError(404, "Report not found");
    -            return;
    -        }
    -
    -        rsp.setContentType("text/html");
    -        try (InputStream in = new FileInputStream(indexFile);
    -             OutputStream out = rsp.getOutputStream()) {
    -            byte[] buffer = new byte[8192];
    -            int bytesRead;
    -            while ((bytesRead = in.read(buffer)) != -1) {
    -                out.write(buffer, 0, bytesRead);
    -            }
    -        }
    +  public void doDynamic(StaplerRequest req, StaplerResponse rsp)
    +      throws IOException, ServletException {
    +    String path = req.getRestOfPath();
    +    if (path.startsWith("/")) {
    +      path = path.substring(1);
         }
     
    -    public void doDynamic(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
    -        String path = req.getRestOfPath();
    -        if (path.startsWith("/")) {
    -            path = path.substring(1);
    -        }
    -
    -        File reportDir = simulation.getSimulationDirectory();
    -        File requestedFile = new File(reportDir, path);
    -
    -        if (!requestedFile.exists() || !requestedFile.getCanonicalPath().startsWith(reportDir.getCanonicalPath())) {
    -            rsp.sendError(404, "File not found");
    -            return;
    -        }
    -
    -        String contentType = getContentType(path);
    -        rsp.setContentType(contentType);
    +    File reportDir = simulation.getSimulationDirectory();
    +    File requestedFile = new File(reportDir, path);
     
    -        try (InputStream in = new FileInputStream(requestedFile);
    -             OutputStream out = rsp.getOutputStream()) {
    -            byte[] buffer = new byte[8192];
    -            int bytesRead;
    -            while ((bytesRead = in.read(buffer)) != -1) {
    -                out.write(buffer, 0, bytesRead);
    -            }
    -        }
    +    if (!requestedFile.exists()
    +        || !requestedFile.getCanonicalPath().startsWith(reportDir.getCanonicalPath())) {
    +      rsp.sendError(404, "File not found");
    +      return;
         }
     
    -    private String getContentType(String path) {
    -        if (path.endsWith(".html")) return "text/html";
    -        if (path.endsWith(".css")) return "text/css";
    -        if (path.endsWith(".js")) return "application/javascript";
    -        if (path.endsWith(".png")) return "image/png";
    -        if (path.endsWith(".jpg") || path.endsWith(".jpeg")) return "image/jpeg";
    -        if (path.endsWith(".gif")) return "image/gif";
    -        if (path.endsWith(".svg")) return "image/svg+xml";
    -        return "application/octet-stream";
    +    // Set security headers
    +    rsp.setHeader(
    +        "Content-Security-Policy",
    +        "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;");
    +    rsp.setHeader("X-Content-Type-Options", "nosniff");
    +    rsp.setHeader("X-Frame-Options", "SAMEORIGIN");
    +
    +    String contentType = getContentType(path);
    +    rsp.setContentType(contentType);
    +
    +    if (path.endsWith(".html")) {
    +      // Read and sanitize HTML content
    +      String htmlContent =
    +          new String(
    +              java.nio.file.Files.readAllBytes(requestedFile.toPath()), StandardCharsets.UTF_8);
    +
    +      // Remove any script tags and their contents
    +      htmlContent = htmlContent.replaceAll("<script[^>]*>.*?</script>", "");
    +      // Remove any event handlers
    +      htmlContent = htmlContent.replaceAll("\\s+on\\w+\\s*=\\s*\"[^\"]*\"", "");
    +      htmlContent = htmlContent.replaceAll("\\s+on\\w+\\s*=\\s*'[^']*'", "");
    +      // Remove any javascript: URLs
    +      htmlContent = htmlContent.replaceAll("javascript:[^\"']*", "");
    +      // Remove any data: URLs except for images
    +      htmlContent = htmlContent.replaceAll("data:(?!image/)[^\"']*", "");
    +
    +      try (OutputStream out = rsp.getOutputStream()) {
    +        out.write(htmlContent.getBytes(StandardCharsets.UTF_8));
    +      }
    +    } else {
    +      // Serve non-HTML files as-is
    +      try (InputStream in = new FileInputStream(requestedFile);
    +          OutputStream out = rsp.getOutputStream()) {
    +        byte[] buffer = new byte[8192];
    +        int bytesRead;
    +        while ((bytesRead = in.read(buffer)) != -1) {
    +          out.write(buffer, 0, bytesRead);
    +        }
    +      }
         }
    -}
    \ No newline at end of file
    +  }
    +
    +  private String getContentType(String path) {
    +    if (path.endsWith(".html")) return "text/html;charset=UTF-8";
    +    if (path.endsWith(".css")) return "text/css;charset=UTF-8";
    +    if (path.endsWith(".js")) return "application/javascript;charset=UTF-8";
    +    if (path.endsWith(".png")) return "image/png";
    +    if (path.endsWith(".jpg") || path.endsWith(".jpeg")) return "image/jpeg";
    +    if (path.endsWith(".gif")) return "image/gif";
    +    if (path.endsWith(".svg")) return "image/svg+xml";
    +    return "application/octet-stream";
    +  }
    +}
    
  • src/main/java/io/gatling/jenkins/RequestReport.java+11 8 modified
    @@ -1,16 +1,14 @@
     /**
      * Copyright 2011-2020 GatlingCorp (http://gatling.io)
      *
    - * 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
    + * <p>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
    + * <p>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
    + * <p>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.gatling.jenkins;
    @@ -26,14 +24,19 @@ public class RequestReport {
       private Statistics maxResponseTime;
       private Statistics meanResponseTime;
       private Statistics standardDeviation;
    +
       @JsonProperty("percentiles1")
       private Statistics percentiles1;
    +
       @JsonProperty("percentiles2")
       private Statistics percentiles2;
    +
       @JsonProperty("percentiles3")
       private Statistics percentiles3;
    +
       @JsonProperty("percentiles4")
       private Statistics percentiles4;
    +
       private Statistics meanNumberOfRequestsPerSecond;
       private ResponseTimeGroup group1;
       private ResponseTimeGroup group2;
    
  • src/main/java/io/gatling/jenkins/ResponseTimeGroup.java+6 8 modified
    @@ -1,16 +1,14 @@
     /**
      * Copyright 2011-2020 GatlingCorp (http://gatling.io)
      *
    - * 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
    + * <p>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
    + * <p>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
    + * <p>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.gatling.jenkins;
    
  • src/main/java/io/gatling/jenkins/SimulationReport.java+19 16 modified
    @@ -1,29 +1,27 @@
     /**
      * Copyright 2011-2020 GatlingCorp (http://gatling.io)
      *
    - * 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
    + * <p>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
    + * <p>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
    + * <p>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.gatling.jenkins;
     
    -import com.fasterxml.jackson.core.type.TypeReference;
    -import com.fasterxml.jackson.databind.DeserializationFeature;
    -import com.fasterxml.jackson.databind.ObjectMapper;
    -import hudson.FilePath;
    -
     import java.io.File;
     import java.io.FileNotFoundException;
     import java.io.IOException;
     
    +import com.fasterxml.jackson.databind.DeserializationFeature;
    +import com.fasterxml.jackson.databind.ObjectMapper;
    +
    +import hudson.FilePath;
    +
     public class SimulationReport {
     
       private final FilePath reportDirectory;
    @@ -45,7 +43,10 @@ public void readStatsFile() throws IOException, InterruptedException {
         File htmlFile = locateStatsFile();
     
         // Read the HTML file content with explicit UTF-8 encoding
    -    String content = new String(java.nio.file.Files.readAllBytes(htmlFile.toPath()), java.nio.charset.StandardCharsets.UTF_8);
    +    String content =
    +        new String(
    +            java.nio.file.Files.readAllBytes(htmlFile.toPath()),
    +            java.nio.charset.StandardCharsets.UTF_8);
     
         // Create a new RequestReport
         RequestReport report = new RequestReport();
    @@ -99,12 +100,14 @@ public void readStatsFile() throws IOException, InterruptedException {
     
           // Max response time (not directly available, using 95th percentile as approximation)
           Statistics maxStats = new Statistics();
    -      maxStats.setTotal(extractNumberFromClass(content, responseTimeIndex + 100)); // Approximate position
    +      maxStats.setTotal(
    +          extractNumberFromClass(content, responseTimeIndex + 100)); // Approximate position
           report.setMaxResponseTime(maxStats);
     
           // Mean response time (not directly available, using 50th percentile as approximation)
           Statistics meanStats = new Statistics();
    -      meanStats.setTotal(extractNumberFromClass(content, responseTimeIndex + 200)); // Approximate position
    +      meanStats.setTotal(
    +          extractNumberFromClass(content, responseTimeIndex + 200)); // Approximate position
           report.setMeanResponseTime(meanStats);
     
           // Percentiles
    
  • src/main/java/io/gatling/jenkins/Statistics.java+6 8 modified
    @@ -1,16 +1,14 @@
     /**
      * Copyright 2011-2020 GatlingCorp (http://gatling.io)
      *
    - * 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
    + * <p>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
    + * <p>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
    + * <p>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.gatling.jenkins;
    
  • src/main/java/io/gatling/jenkins/steps/GatlingArchiverStepExecution.java+23 27 modified
    @@ -1,50 +1,46 @@
     /**
      * Copyright 2011-2020 GatlingCorp (http://gatling.io)
      *
    - * 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
    + * <p>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
    + * <p>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
    + * <p>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.gatling.jenkins.steps;
     
    +import org.jenkinsci.plugins.workflow.steps.AbstractSynchronousNonBlockingStepExecution;
    +import org.jenkinsci.plugins.workflow.steps.StepContextParameter;
    +
     import hudson.FilePath;
     import hudson.Launcher;
     import hudson.model.Run;
     import hudson.model.TaskListener;
     import io.gatling.jenkins.GatlingPublisher;
    -import org.jenkinsci.plugins.workflow.steps.AbstractSynchronousNonBlockingStepExecution;
    -import org.jenkinsci.plugins.workflow.steps.StepContextParameter;
     
    -public class GatlingArchiverStepExecution extends AbstractSynchronousNonBlockingStepExecution<Void> {
    -    private static final long serialVersionUID = 1L;
    +public class GatlingArchiverStepExecution
    +    extends AbstractSynchronousNonBlockingStepExecution<Void> {
    +  private static final long serialVersionUID = 1L;
     
    -    @StepContextParameter
    -    private transient TaskListener listener;
    +  @StepContextParameter private transient TaskListener listener;
     
    -    @StepContextParameter
    -    private transient FilePath ws;
    +  @StepContextParameter private transient FilePath ws;
     
    -    @StepContextParameter
    -    private transient Run build;
    +  @StepContextParameter private transient Run build;
     
    -    @StepContextParameter
    -    private transient Launcher launcher;
    +  @StepContextParameter private transient Launcher launcher;
     
    -    @Override
    -    protected Void run() throws Exception {
    -        listener.getLogger().println("Running Gatling archiver step.");
    +  @Override
    +  protected Void run() throws Exception {
    +    listener.getLogger().println("Running Gatling archiver step.");
     
    -        GatlingPublisher publisher = new GatlingPublisher(true);
    -        publisher.perform(build, ws, launcher, listener);
    +    GatlingPublisher publisher = new GatlingPublisher(true);
    +    publisher.perform(build, ws, launcher, listener);
     
    -        return null;
    -    }
    +    return null;
    +  }
     }
    
  • src/main/java/io/gatling/jenkins/steps/GatlingArchiverStep.java+26 27 modified
    @@ -1,47 +1,46 @@
     /**
      * Copyright 2011-2020 GatlingCorp (http://gatling.io)
      *
    - * 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
    + * <p>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
    + * <p>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
    + * <p>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.gatling.jenkins.steps;
     
    -import hudson.Extension;
    +import javax.annotation.Nonnull;
    +
     import org.jenkinsci.plugins.workflow.steps.AbstractStepDescriptorImpl;
     import org.jenkinsci.plugins.workflow.steps.AbstractStepImpl;
     import org.kohsuke.stapler.DataBoundConstructor;
     
    -import javax.annotation.Nonnull;
    +import hudson.Extension;
     
    -/**
    - * Archiving for gatling reports.
    - */
    +/** Archiving for gatling reports. */
     public class GatlingArchiverStep extends AbstractStepImpl {
    -    @DataBoundConstructor
    -    public GatlingArchiverStep() {}
    +  @DataBoundConstructor
    +  public GatlingArchiverStep() {}
     
    -    @Extension
    -    public static class DescriptorImpl extends AbstractStepDescriptorImpl {
    -        public DescriptorImpl() { super(GatlingArchiverStepExecution.class); }
    +  @Extension
    +  public static class DescriptorImpl extends AbstractStepDescriptorImpl {
    +    public DescriptorImpl() {
    +      super(GatlingArchiverStepExecution.class);
    +    }
     
    -        @Override
    -        public String getFunctionName() {
    -            return "gatlingArchive";
    -        }
    +    @Override
    +    public String getFunctionName() {
    +      return "gatlingArchive";
    +    }
     
    -        @Nonnull
    -        @Override
    -        public String getDisplayName() {
    -            return "Archive Gatling reports";
    -        }
    +    @Nonnull
    +    @Override
    +    public String getDisplayName() {
    +      return "Archive Gatling reports";
         }
    +  }
     }
    
  • src/main/java/io/gatling/jenkins/ZipSimulationUtil.java+22 25 modified
    @@ -1,16 +1,14 @@
     /**
      * Copyright 2011-2020 GatlingCorp (http://gatling.io)
      *
    - * 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
    + * <p>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
    + * <p>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
    + * <p>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.gatling.jenkins;
    @@ -20,27 +18,26 @@
     import java.util.logging.Level;
     import java.util.logging.Logger;
     
    -
     import hudson.FilePath;
     
    -public final  class ZipSimulationUtil {
    -    private static final Logger LOGGER = Logger.getLogger(ZipSimulationUtil.class.getName());
    +public final class ZipSimulationUtil {
    +  private static final Logger LOGGER = Logger.getLogger(ZipSimulationUtil.class.getName());
     
    -    private static File zipSimulation(FilePath simulationFilePath) throws InterruptedException {
    -        File zippedFile = new File(simulationFilePath + ".zip");
    -        try {
    -            simulationFilePath.zip(new FilePath(zippedFile));
    -        } catch (IOException e) {
    -            LOGGER.log(Level.INFO, e.getMessage(), e);
    -        }
    -        return zippedFile;
    +  private static File zipSimulation(FilePath simulationFilePath) throws InterruptedException {
    +    File zippedFile = new File(simulationFilePath + ".zip");
    +    try {
    +      simulationFilePath.zip(new FilePath(zippedFile));
    +    } catch (IOException e) {
    +      LOGGER.log(Level.INFO, e.getMessage(), e);
         }
    +    return zippedFile;
    +  }
     
    -    public static File getSimulationZip(File simulationDirectory) throws InterruptedException {
    -        File simulationFile = new File(simulationDirectory.getAbsolutePath() + ".zip");
    -        if (!simulationFile.exists()) {
    -            return zipSimulation(new FilePath(simulationDirectory));
    -        }
    -        return simulationFile;
    +  public static File getSimulationZip(File simulationDirectory) throws InterruptedException {
    +    File simulationFile = new File(simulationDirectory.getAbsolutePath() + ".zip");
    +    if (!simulationFile.exists()) {
    +      return zipSimulation(new FilePath(simulationDirectory));
         }
    +    return simulationFile;
    +  }
     }
    
  • src/main/resources/io/gatling/jenkins/GatlingBuildAction/index.jelly+16 4 modified
    @@ -6,11 +6,23 @@
     			<st:include it="${it.run}" page="sidepanel.jelly"/>
     		</l:side-panel>
     		<l:main-panel>
    -            <h2>${%AvailableReports} :</h2>
    -            <ul>
    +            <h2>Gatling Performance Reports(Without Graphs):</h2>
    +            <ul style="list-style-type: none; padding-left: 0;">
                     <j:forEach items="${it.simulations}" var="sim">
    -                    <li>
    -                        <a href="../${it.getReportURL(sim)}" target="_blank">${sim.getSimulationDirectory().getName()}</a>
    +                    <li style="margin-bottom: 20px;">
    +                        <div style="margin-bottom: 10px;">
    +                            <a href="../${it.getReportURL(sim)}" target="_blank" style="font-weight: bold; font-size: 16px;">${sim.getSimulationDirectory().getName()}</a>
    +                        </div>
    +                        <div style="margin-left: 20px;">
    +                            <h2 style="font-size: 20px; color: white; margin: 10px 0;">Download Options:</h2>
    +                            <ul style="list-style-type: disc; margin-left: 20px;">
    +                                <li style="margin-bottom: 5px;">
    +                                    <a href="../${it.getDownloadURL(sim)}" class="download-link" style="font-weight: bold; font-size: 16px;">
    +                                        Download the report with graphs
    +                                    </a>
    +                                </li>
    +                            </ul>
    +                        </div>
                         </li>
                     </j:forEach>
                 </ul>
    
  • src/main/resources/io/gatling/jenkins/GatlingBuildAction/index.properties+1 1 modified
    @@ -1 +1 @@
    -AvailableReports=Available reports for this build
    +AvailableReports=Gatling Performance Reports for this build(Without Graphs)
    
  • src/main/resources/io/gatling/jenkins/GatlingBuildAction/summary.properties+1 1 modified
    @@ -1 +1 @@
    -AvailableReports=Available reports for this build
    +AvailableReports=Gatling Performance Reports for this build(Without Graphs)
    
  • src/main/resources/io/gatling/jenkins/GatlingProjectAction/index.properties+1 1 modified
    @@ -1,4 +1,4 @@
    -ProjectPageTitle=Performance Trend
    +ProjectPageTitle=Performance Trend(Enterprise Only)
     AvailableReports=Available reports for this project
     MeanResponseTimeChartTitle=Mean response time
     ResponseTimePercentileChartTitle=95th percentile response time
    
  • src/test/java/io/gatling/jenkins/steps/GatlingArchiverStepTest.java+104 98 modified
    @@ -1,28 +1,24 @@
     /**
      * Copyright 2011-2020 GatlingCorp (http://gatling.io)
      *
    - * 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
    + * <p>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
    + * <p>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
    + * <p>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.gatling.jenkins.steps;
     
    -import hudson.model.Action;
    -import hudson.model.FreeStyleBuild;
    -import hudson.model.FreeStyleProject;
    -import hudson.model.Run;
    -import hudson.slaves.DumbSlave;
    -import hudson.tasks.Shell;
    -import io.gatling.jenkins.GatlingBuildAction;
    -import io.gatling.jenkins.GatlingPublisher;
    +import java.io.File;
    +import java.util.ArrayList;
    +import java.util.Arrays;
    +import java.util.List;
    +
    +import org.apache.commons.io.FileUtils;
     import org.apache.commons.lang.StringUtils;
     import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
     import org.jenkinsci.plugins.workflow.job.WorkflowJob;
    @@ -34,89 +30,99 @@
     import org.jvnet.hudson.test.Issue;
     import org.jvnet.hudson.test.JenkinsRule;
     
    -import java.io.File;
    -import java.util.ArrayList;
    -import java.util.Arrays;
    -import java.util.List;
    -import org.apache.commons.io.FileUtils;
    +import hudson.model.Action;
    +import hudson.model.FreeStyleBuild;
    +import hudson.model.FreeStyleProject;
    +import hudson.model.Run;
    +import hudson.slaves.DumbSlave;
    +import hudson.tasks.Shell;
    +import io.gatling.jenkins.GatlingBuildAction;
    +import io.gatling.jenkins.GatlingPublisher;
     
     public class GatlingArchiverStepTest extends Assert {
    -    @Rule
    -    public JenkinsRule j = new JenkinsRule();
    -
    -    /**
    -     * Test archiving of gatling reports
    -     */
    -    @Test
    -    public void archive() throws Exception {
    -        // job setup
    -        WorkflowJob foo = j.jenkins.createProject(WorkflowJob.class, "foo");
    -        foo.setDefinition(new CpsFlowDefinition(StringUtils.join(Arrays.asList(
    -            "node {",
    -            "  sleep 1", // JENKINS-51015
    -            "  writeFile file: 'results/foo-1234/index.html', text: '<html><body><div id=\"container_statistics_body\"><table><tbody><tr class=\"total col-1\"><td>Total</td><td class=\"value total col-2\">100</td><td class=\"value ok col-3\">95</td><td class=\"value ko col-4\">5</td><td class=\"value total col-7\">50</td></tr></tbody></table></div></body></html>'",
    -            "  writeFile file: 'results/foo-4321/index.html', text: '<html><body><div id=\"container_statistics_body\"><table><tbody><tr class=\"total col-1\"><td>Total</td><td class=\"value total col-2\">100</td><td class=\"value ok col-3\">95</td><td class=\"value ko col-4\">5</td><td class=\"value total col-7\">50</td></tr></tbody></table></div></body></html>'",
    -            "  writeFile file: 'results/bar-5678/index.html', text: '<html><body><div id=\"container_statistics_body\"><table><tbody><tr class=\"total col-1\"><td>Total</td><td class=\"value total col-2\">100</td><td class=\"value ok col-3\">95</td><td class=\"value ko col-4\">5</td><td class=\"value total col-7\">50</td></tr></tbody></table></div></body></html>'",
    -            "  gatlingArchive()",
    -            "}"), "\n")));
    -
    -        // get the build going, and wait until workflow pauses
    -        WorkflowRun b = j.assertBuildStatusSuccess(foo.scheduleBuild2(0).get());
    -        verifyResult(b);
    -    }
    -
    -    @Test
    -    @Issue("JENKINS-50977")
    -    public void archiveInFreestyle() throws Exception {
    -        Assume.assumeFalse("The test is Unix-only",
    -            hudson.remoting.Launcher.isWindows());
    -        FreeStyleProject foo = j.createFreeStyleProject();
    -        DumbSlave onlineSlave = j.createOnlineSlave();
    -
    -        foo.setAssignedNode(onlineSlave);
    -        foo.getBuildersList().add(new Shell("\n" +
    -            "sleep 1 \n" + // Otherwise GatlingPublisher skips that because BuildStart time has second-accuracy (JENKINS-51015)
    -            "mkdir -p results/foo-1234/\n" +
    -            "echo '<html><body><div id=\"container_statistics_body\"><table><tbody><tr class=\"total col-1\"><td>Total</td><td class=\"value total col-2\">100</td><td class=\"value ok col-3\">95</td><td class=\"value ko col-4\">5</td><td class=\"value total col-7\">50</td></tr></tbody></table></div></body></html>' > results/foo-1234/index.html\n" +
    -            "mkdir -p results/foo-4321/\n" +
    -            "echo '<html><body><div id=\"container_statistics_body\"><table><tbody><tr class=\"total col-1\"><td>Total</td><td class=\"value total col-2\">100</td><td class=\"value ok col-3\">95</td><td class=\"value ko col-4\">5</td><td class=\"value total col-7\">50</td></tr></tbody></table></div></body></html>' > results/foo-4321/index.html\n" +
    -            "mkdir -p results/bar-5678/\n" +
    -            "echo '<html><body><div id=\"container_statistics_body\"><table><tbody><tr class=\"total col-1\"><td>Total</td><td class=\"value total col-2\">100</td><td class=\"value ok col-3\">95</td><td class=\"value ko col-4\">5</td><td class=\"value total col-7\">50</td></tr></tbody></table></div></body></html>' > results/bar-5678/index.html\n"
    -        ));
    -        foo.getPublishersList().add(new GatlingPublisher(true));
    -
    -        // Build and assert status
    -        FreeStyleBuild freeStyleBuild = j.buildAndAssertSuccess(foo);
    -        verifyResult(freeStyleBuild);
    -
    -        // Triggers JEP-200 if PrintStream is still persisted
    -        foo.save();
    -    }
    -
    -    private void verifyResult(Run b) throws Exception {
    -        File baseDir = b.getRootDir();
    -        File fooArchiveDir = new File(baseDir, "simulations/foo-1234");
    -        assertTrue("foo archive dir doesn't exist: " + fooArchiveDir, fooArchiveDir.isDirectory());
    -
    -        File foo2ArchiveDir = new File(baseDir, "simulations/foo-4321");
    -        assertTrue("foo archive dir doesn't exist: " + foo2ArchiveDir, foo2ArchiveDir.isDirectory());
    -
    -        File barArchiveDir = new File(baseDir, "simulations/bar-5678");
    -        assertTrue("bar archive dir doesn't exist: " + barArchiveDir, barArchiveDir.isDirectory());
    -
    -        List<GatlingBuildAction> gbas = new ArrayList<>();
    -        for (Action a : b.getAllActions()) {
    -            if (a instanceof GatlingBuildAction) {
    -                gbas.add((GatlingBuildAction) a);
    -            }
    -        }
    -        assertEquals("Should be exactly one GatlingBuildAction", 1, gbas.size());
    -
    -        GatlingBuildAction buildAction = gbas.get(0);
    -        assertEquals("BuildAction should have exactly one ProjectAction", 1, buildAction.getProjectActions().size());
    -
    -        // TODO JENKINS-57244 use RestartableJenkinsRule to verify that the action was actually saved successfully
    -        FileUtils.copyFile(new File(b.getRootDir(), "build.xml"), System.out);
    +  @Rule public JenkinsRule j = new JenkinsRule();
    +
    +  /** Test archiving of gatling reports */
    +  @Test
    +  public void archive() throws Exception {
    +    // job setup
    +    WorkflowJob foo = j.jenkins.createProject(WorkflowJob.class, "foo");
    +    foo.setDefinition(
    +        new CpsFlowDefinition(
    +            StringUtils.join(
    +                Arrays.asList(
    +                    "node {",
    +                    "  sleep 1", // JENKINS-51015
    +                    "  writeFile file: 'results/foo-1234/index.html', text: '<html><body><div id=\"container_statistics_body\"><table><tbody><tr class=\"total col-1\"><td>Total</td><td class=\"value total col-2\">100</td><td class=\"value ok col-3\">95</td><td class=\"value ko col-4\">5</td><td class=\"value total col-7\">50</td></tr></tbody></table></div></body></html>'",
    +                    "  writeFile file: 'results/foo-4321/index.html', text: '<html><body><div id=\"container_statistics_body\"><table><tbody><tr class=\"total col-1\"><td>Total</td><td class=\"value total col-2\">100</td><td class=\"value ok col-3\">95</td><td class=\"value ko col-4\">5</td><td class=\"value total col-7\">50</td></tr></tbody></table></div></body></html>'",
    +                    "  writeFile file: 'results/bar-5678/index.html', text: '<html><body><div id=\"container_statistics_body\"><table><tbody><tr class=\"total col-1\"><td>Total</td><td class=\"value total col-2\">100</td><td class=\"value ok col-3\">95</td><td class=\"value ko col-4\">5</td><td class=\"value total col-7\">50</td></tr></tbody></table></div></body></html>'",
    +                    "  gatlingArchive()",
    +                    "}"),
    +                "\n")));
    +
    +    // get the build going, and wait until workflow pauses
    +    WorkflowRun b = j.assertBuildStatusSuccess(foo.scheduleBuild2(0).get());
    +    verifyResult(b);
    +  }
    +
    +  @Test
    +  @Issue("JENKINS-50977")
    +  public void archiveInFreestyle() throws Exception {
    +    Assume.assumeFalse("The test is Unix-only", hudson.remoting.Launcher.isWindows());
    +    FreeStyleProject foo = j.createFreeStyleProject();
    +    DumbSlave onlineSlave = j.createOnlineSlave();
    +
    +    foo.setAssignedNode(onlineSlave);
    +    foo.getBuildersList()
    +        .add(
    +            new Shell(
    +                "\n"
    +                    + "sleep 1 \n"
    +                    + // Otherwise GatlingPublisher skips that because BuildStart time has
    +                    // second-accuracy (JENKINS-51015)
    +                    "mkdir -p results/foo-1234/\n"
    +                    + "echo '<html><body><div id=\"container_statistics_body\"><table><tbody><tr class=\"total col-1\"><td>Total</td><td class=\"value total col-2\">100</td><td class=\"value ok col-3\">95</td><td class=\"value ko col-4\">5</td><td class=\"value total col-7\">50</td></tr></tbody></table></div></body></html>' > results/foo-1234/index.html\n"
    +                    + "mkdir -p results/foo-4321/\n"
    +                    + "echo '<html><body><div id=\"container_statistics_body\"><table><tbody><tr class=\"total col-1\"><td>Total</td><td class=\"value total col-2\">100</td><td class=\"value ok col-3\">95</td><td class=\"value ko col-4\">5</td><td class=\"value total col-7\">50</td></tr></tbody></table></div></body></html>' > results/foo-4321/index.html\n"
    +                    + "mkdir -p results/bar-5678/\n"
    +                    + "echo '<html><body><div id=\"container_statistics_body\"><table><tbody><tr class=\"total col-1\"><td>Total</td><td class=\"value total col-2\">100</td><td class=\"value ok col-3\">95</td><td class=\"value ko col-4\">5</td><td class=\"value total col-7\">50</td></tr></tbody></table></div></body></html>' > results/bar-5678/index.html\n"));
    +    foo.getPublishersList().add(new GatlingPublisher(true));
    +
    +    // Build and assert status
    +    FreeStyleBuild freeStyleBuild = j.buildAndAssertSuccess(foo);
    +    verifyResult(freeStyleBuild);
    +
    +    // Triggers JEP-200 if PrintStream is still persisted
    +    foo.save();
    +  }
    +
    +  private void verifyResult(Run b) throws Exception {
    +    File baseDir = b.getRootDir();
    +    File fooArchiveDir = new File(baseDir, "simulations/foo-1234");
    +    assertTrue("foo archive dir doesn't exist: " + fooArchiveDir, fooArchiveDir.isDirectory());
    +
    +    File foo2ArchiveDir = new File(baseDir, "simulations/foo-4321");
    +    assertTrue("foo archive dir doesn't exist: " + foo2ArchiveDir, foo2ArchiveDir.isDirectory());
    +
    +    File barArchiveDir = new File(baseDir, "simulations/bar-5678");
    +    assertTrue("bar archive dir doesn't exist: " + barArchiveDir, barArchiveDir.isDirectory());
    +
    +    List<GatlingBuildAction> gbas = new ArrayList<>();
    +    for (Action a : b.getAllActions()) {
    +      if (a instanceof GatlingBuildAction) {
    +        gbas.add((GatlingBuildAction) a);
    +      }
         }
    +    assertEquals("Should be exactly one GatlingBuildAction", 1, gbas.size());
    +
    +    GatlingBuildAction buildAction = gbas.get(0);
    +    assertEquals(
    +        "BuildAction should have exactly one ProjectAction",
    +        1,
    +        buildAction.getProjectActions().size());
    +
    +    // TODO JENKINS-57244 use RestartableJenkinsRule to verify that the action was actually saved
    +    // successfully
    +    FileUtils.copyFile(new File(b.getRootDir(), "build.xml"), System.out);
    +  }
     }
    -
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

1