diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 456f8fca6da7..a2ed88450df5 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -28,16 +28,17 @@ For refactoring and code cleanup changes, exercise the code before and after the
### Proposed changelog entries
-- JENKINS-XXXXX, human-readable text
+- human-readable text
@@ -45,6 +46,11 @@ You may add multiple changelog entries if applicable by adding a new entry to th
N/A
+
+
```[tasklist]
### Submitter checklist
- [ ] The Jira issue, if it exists, is well-described.
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
deleted file mode 100644
index a44d07b50c54..000000000000
--- a/.github/dependabot.yml
+++ /dev/null
@@ -1,68 +0,0 @@
----
-version: 2
-updates:
- - package-ecosystem: "github-actions"
- directory: "/"
- schedule:
- interval: "daily"
- - package-ecosystem: "maven"
- directory: "/"
- schedule:
- interval: "daily"
- ignore:
- # Exclusions in this section have been triaged and determined to be
- # permanent. We do not anticipate removing exclusions from this section.
-
- # Provided by Jetty and should be aligned with the version provided by the
- # version of Jetty we deliver. See:
- # https://github.com/jenkinsci/jenkins/pull/5211
- - dependency-name: "jakarta.servlet:jakarta.servlet-api"
-
- # Jetty Maven Plugin and Winstone should be upgraded in lockstep in order
- # to keep their corresponding Jetty versions aligned.
- - dependency-name: "org.eclipse.jetty:jetty-maven-plugin"
- - dependency-name: "org.jenkins-ci:winstone"
-
- # Here lies technical debt. Exclusions in this section have been triaged
- # and determined to be temporary. Exclusions should be removed from this
- # section once the remaining action items have been completed.
-
- # Contains incompatible API changes and needs compatibility work.
- - dependency-name: "jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api"
-
- # Needs significant testing. See:
- # https://github.com/jenkinsci/jenkins/pull/5112#issuecomment-744429487
- # https://github.com/jenkinsci/jenkins/pull/5116#issuecomment-744526638
- - dependency-name: "org.codehaus.groovy:groovy-all"
- versions: [">=2.5.0"]
-
- # Consumed by Groovy and should be updated in lockstep with Groovy. See:
- # https://github.com/jenkinsci/jenkins/pull/5184
- - dependency-name: "org.fusesource.jansi:jansi"
-
- # Contains incompatible API changes and needs compatibility work. See:
- # https://github.com/jenkinsci/jenkins/pull/4224
- - dependency-name: "org.jfree:jfreechart"
-
- # Starting with 6.x, Spring requires Java 17 at a minimum.
- - dependency-name: "org.springframework:spring-framework-bom"
- versions: [">=6.0.0"]
-
- # Starting with 6.x, Spring Security requires Java 17 at a minimum.
- - dependency-name: "org.springframework.security:spring-security-bom"
- versions: [">=6.0.0"]
-
- # Starting with 7.x, Guice switches from javax.* to jakarta.* bindings.
- # See https://github.com/google/guice/wiki/Guice700
- - dependency-name: "com.google.inject:guice-bom"
- versions: [">=7.0.0"]
- - package-ecosystem: "maven"
- directory: "/"
- target-branch: "stable-2.440"
- labels:
- - "into-lts"
- - "needs-justification"
- schedule:
- interval: "daily"
- # Include only security updates and exclude version updates.
- open-pull-requests-limit: 0
diff --git a/.github/renovate.json b/.github/renovate.json
index 8c3c4ad17cea..aab994ad8187 100644
--- a/.github/renovate.json
+++ b/.github/renovate.json
@@ -1,63 +1,221 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
- "config:base",
+ "config:recommended",
":disableDependencyDashboard",
":semanticCommitsDisabled"
],
- "enabledManagers": ["npm", "regex"],
- "postUpdateOptions": ["yarnDedupeHighest"],
+ "prHourlyLimit": 0,
+ "prConcurrentLimit": 0,
+ "postUpdateOptions": [
+ "yarnDedupeHighest"
+ ],
"packageRules": [
{
- "matchDatasources": ["npm"],
- "addLabels": ["javascript"],
- "stabilityDays": 3,
- "reviewers": ["team:sig-ux"]
+ "matchDatasources": [
+ "npm"
+ ],
+ "addLabels": [
+ "javascript"
+ ],
+ "minimumReleaseAge": "3 days",
+ "reviewers": [
+ "team:sig-ux"
+ ]
},
{
- "matchPackageNames": ["node"],
+ "matchPackageNames": [
+ "node"
+ ],
"allowedVersions": "/20.[0-9]+.[0-9]+(.[0-9]+)?$/"
+ },
+ {
+ "description": "Should be upgraded in lockstep in order to keep their corresponding Jetty versions aligned, could be grouped but releases are likely separated by a bit of time",
+ "matchManagers": [
+ "maven"
+ ],
+ "enabled": false,
+ "matchPackageNames": [
+ "org.eclipse.jetty:jetty-maven-plugin",
+ "org.jenkins-ci:winstone"
+ ]
+ },
+ {
+ "description": "Provided by Jetty and should be aligned with the version provided by the version of Jetty we deliver. See: https://github.com/jenkinsci/jenkins/pull/5211",
+ "matchManagers": [
+ "maven"
+ ],
+ "enabled": false,
+ "matchPackageNames": [
+ "jakarta.servlet:jakarta.servlet-api",
+ "jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api"
+ ]
+ },
+ {
+ "description": "Needs significant testing. See: https://github.com/jenkinsci/jenkins/pull/5112#issuecomment-744429487 and https://github.com/jenkinsci/jenkins/pull/5116#issuecomment-744526638",
+ "matchManagers": [
+ "maven"
+ ],
+ "allowedVersions": "<2.5.0",
+ "matchPackageNames": [
+ "org.codehaus.groovy:groovy-all"
+ ]
+ },
+ {
+ "description": "Consumed by Groovy and should be updated in lockstep with Groovy. See: https://github.com/jenkinsci/jenkins/pull/5184",
+ "matchManagers": [
+ "maven"
+ ],
+ "enabled": false,
+ "matchPackageNames": [
+ "org.fusesource.jansi:jansi"
+ ]
+ },
+ {
+ "description": "Depends on commons-lang3 which is in progress for removal from core. See: https://issues.jenkins.io/browse/JENKINS-73355",
+ "matchManagers": [
+ "maven"
+ ],
+ "enabled": false,
+ "matchPackageNames": [
+ "org.apache.commons:commons-compress"
+ ]
+ },
+ {
+ "description": "Contains incompatible API changes and needs compatibility work. See: https://github.com/jenkinsci/jenkins/pull/4224",
+ "matchManagers": [
+ "maven"
+ ],
+ "enabled": false,
+ "matchPackageNames": [
+ "org.jfree:jfreechart"
+ ]
+ },
+ {
+ "description": "Starting with 6.x, Spring requires Java 17 at a minimum.",
+ "matchManagers": [
+ "maven"
+ ],
+ "allowedVersions": "<6.0.0",
+ "matchPackageNames": [
+ "org.springframework:spring-framework-bom",
+ "org.springframework.security:spring-security-bom"
+ ]
+ },
+ {
+ "description": "Starting with 7.x, Guice switches from javax.* to jakarta.* bindings. See https://github.com/google/guice/wiki/Guice700",
+ "matchManagers": [
+ "maven"
+ ],
+ "allowedVersions": "<7.0.0",
+ "matchPackageNames": [
+ "com.google.inject:guice-bom"
+ ]
+ },
+ {
+ "matchFileNames": [
+ "core/pom.xml",
+ "test/pom.xml",
+ "war/pom.xml"
+ ],
+ "matchPackageNames": [
+ "org.jenkins-ci.main:remoting"
+ ],
+ "description": "Avoid updating the remoting.minimum.supported.version property but still update latest one by not placing this property in the parent pom.xml",
+ "enabled": false
+ },
+ {
+ "matchPackageNames": [
+ "net.jcip:jcip-annotations"
+ ],
+ "matchDatasources": [
+ "maven"
+ ],
+ "enabled": false,
+ "description": "maven-metadata.xml is missing for this really old package which is required by renovate"
}
],
- "regexManagers": [
+ "customManagers": [
{
- "fileMatch": ["war/pom.xml"],
- "matchStrings": ["(?.*?)"],
+ "customType": "regex",
+ "fileMatch": [
+ "war/pom.xml"
+ ],
+ "matchStrings": [
+ "(?.*?)"
+ ],
"depNameTemplate": "node",
"datasourceTemplate": "npm"
},
{
- "fileMatch": ["ath.sh"],
- "matchStrings": ["export ATH_VERSION=(?.*?)\n"],
+ "customType": "regex",
+ "fileMatch": [
+ "ath.sh"
+ ],
+ "matchStrings": [
+ "export ATH_VERSION=(?.*?)\n"
+ ],
"depNameTemplate": "jenkins/ath",
"datasourceTemplate": "docker",
"versioningTemplate": "loose"
},
{
- "fileMatch": [".gitpod/Dockerfile"],
- "matchStrings": ["ARG MAVEN_VERSION=(?.*?)\n"],
+ "customType": "regex",
+ "fileMatch": [
+ ".gitpod/Dockerfile"
+ ],
+ "matchStrings": [
+ "ARG MAVEN_VERSION=(?.*?)\n"
+ ],
"depNameTemplate": "org.apache.maven:maven-core",
"datasourceTemplate": "maven"
},
{
- "fileMatch": ["core/src/site/site.xml"],
- "matchStrings": ["lit@(?.*?)/"],
+ "customType": "regex",
+ "fileMatch": [
+ "core/src/site/site.xml"
+ ],
+ "matchStrings": [
+ "lit@(?.*?)/"
+ ],
"depNameTemplate": "lit",
"datasourceTemplate": "npm"
},
{
- "fileMatch": ["core/src/site/site.xml"],
- "matchStrings": ["webcomponentsjs@(?.*?)/"],
+ "customType": "regex",
+ "fileMatch": [
+ "core/src/site/site.xml"
+ ],
+ "matchStrings": [
+ "webcomponentsjs@(?.*?)/"
+ ],
"depNameTemplate": "@webcomponents/webcomponentsjs",
"datasourceTemplate": "npm"
},
{
- "fileMatch": ["core/src/site/site.xml"],
- "matchStrings": ["(?.*?)<\/version>"],
+ "customType": "regex",
+ "fileMatch": [
+ "core/src/site/site.xml"
+ ],
+ "matchStrings": [
+ "(?.*?)"
+ ],
"depNameTemplate": "org.apache.maven.skins:maven-fluido-skin",
"datasourceTemplate": "maven"
}
],
- "labels": ["dependencies", "skip-changelog"],
- "rebaseWhen": "conflicted"
+ "labels": [
+ "dependencies",
+ "skip-changelog"
+ ],
+ "rebaseWhen": "conflicted",
+ "ignorePaths": [
+ "**/node_modules/**",
+ "**/bower_components/**",
+ "**/vendor/**",
+ "**/examples/**",
+ "**/__tests__/**",
+ "**/tests/**",
+ "**/__fixtures__/**"
+ ]
}
diff --git a/.github/workflows/label-conflicting-pr.yml b/.github/workflows/label-conflicting-pr.yml
index 80079296c113..8b78edd6004c 100644
--- a/.github/workflows/label-conflicting-pr.yml
+++ b/.github/workflows/label-conflicting-pr.yml
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Label conflicting PRs
- uses: eps1lon/actions-label-merge-conflict@v3.0.0
+ uses: eps1lon/actions-label-merge-conflict@v3.0.2
with:
dirtyLabel: "unresolved-merge-conflict"
repoToken: "${{ secrets.GITHUB_TOKEN }}"
diff --git a/.github/workflows/publish-release-artifact.yml b/.github/workflows/publish-release-artifact.yml
index 968df18529c9..8b7242136ce9 100644
--- a/.github/workflows/publish-release-artifact.yml
+++ b/.github/workflows/publish-release-artifact.yml
@@ -73,7 +73,7 @@ jobs:
wget -q https://get.jenkins.io/${REPO}/${PROJECT_VERSION}/${FILE_NAME}
- name: Upload Release Asset
id: upload-war
- uses: softprops/action-gh-release@9d7c94cfd0a1f3ed45544c887983e9fa900f0564
+ uses: softprops/action-gh-release@c062e08bd532815e2082a85e87e3ef29c3e6d191 # v2.0.8
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
@@ -108,7 +108,7 @@ jobs:
- name: Upload Release Asset
id: upload-deb
if: always()
- uses: softprops/action-gh-release@9d7c94cfd0a1f3ed45544c887983e9fa900f0564
+ uses: softprops/action-gh-release@c062e08bd532815e2082a85e87e3ef29c3e6d191 # v2.0.8
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
@@ -144,7 +144,7 @@ jobs:
- name: Upload Release Asset
id: upload-rpm
if: always()
- uses: softprops/action-gh-release@9d7c94cfd0a1f3ed45544c887983e9fa900f0564
+ uses: softprops/action-gh-release@c062e08bd532815e2082a85e87e3ef29c3e6d191 # v2.0.8
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
@@ -180,7 +180,7 @@ jobs:
- name: Upload Release Asset
id: upload-msi
if: always()
- uses: softprops/action-gh-release@9d7c94cfd0a1f3ed45544c887983e9fa900f0564
+ uses: softprops/action-gh-release@c062e08bd532815e2082a85e87e3ef29c3e6d191 # v2.0.8
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
@@ -216,7 +216,7 @@ jobs:
- name: Upload Release Asset
id: upload-suse-rpm
if: always()
- uses: softprops/action-gh-release@9d7c94cfd0a1f3ed45544c887983e9fa900f0564
+ uses: softprops/action-gh-release@c062e08bd532815e2082a85e87e3ef29c3e6d191 # v2.0.8
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
diff --git a/.gitpod/Dockerfile b/.gitpod/Dockerfile
index 6b71c6707163..58d3fa8a87f4 100644
--- a/.gitpod/Dockerfile
+++ b/.gitpod/Dockerfile
@@ -1,6 +1,6 @@
FROM gitpod/workspace-full
-ARG MAVEN_VERSION=3.9.6
+ARG MAVEN_VERSION=3.9.9
RUN brew install gh && \
bash -c ". /home/gitpod/.sdkman/bin/sdkman-init.sh && sdk install maven ${MAVEN_VERSION} && sdk default maven ${MAVEN_VERSION}"
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
index de5572116383..ca018ebc3ab9 100644
--- a/.idea/encodings.xml
+++ b/.idea/encodings.xml
@@ -32,6 +32,9 @@
+
+
+
diff --git a/.mvn/extensions.xml b/.mvn/extensions.xml
index a64ab6f242ca..30a03ea18d31 100644
--- a/.mvn/extensions.xml
+++ b/.mvn/extensions.xml
@@ -2,6 +2,6 @@
io.jenkins.tools.incrementalsgit-changelist-maven-extension
- 1.7
+ 1.8
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 77c012b44c68..5933560a951b 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -9,13 +9,11 @@ This page provides information about contributing code to the Jenkins core codeb
1. Fork the repository on GitHub
2. Clone the forked repository to your machine
3. Install the necessary development tools. In order to develop Jenkins, you need the following:
- - Java Development Kit (JDK) 11, 17 or 21.
+ - Java Development Kit (JDK) 17 or 21.
In the Jenkins project we usually use [Eclipse Temurin](https://adoptium.net/) or [OpenJDK](https://openjdk.java.net/), but you can use other JDKs as well.
- Apache Maven 3.8.1 or above. You can [download Maven here](https://maven.apache.org/download.cgi).
In the Jenkins project we usually use the most recent Maven release.
- Any IDE which supports importing Maven projects.
- - Install [Node.js 20.x](https://nodejs.org/en/). **Note:** only needed to work on the frontend assets found in the `war` module.
- - Frontend tasks are run using [yarn](https://yarnpkg.com/). Run `npm install -g yarn` to install it.
4. Set up your development environment as described in [Preparing for Plugin Development](https://www.jenkins.io/doc/developer/tutorial/prepare/)
If you want to contribute to Jenkins, or just learn about the project,
@@ -28,6 +26,8 @@ You can find them by using this query (check the link) for [newbie friendly issu
The Jenkins core build flow is built around Maven.
You can read a description of the [building and debugging process here](https://www.jenkins.io/doc/developer/building/).
+### Building the WAR file
+
If you want simply to build the `jenkins.war` file as fast as possible without tests, run:
```sh
@@ -40,14 +40,31 @@ If you want to debug the WAR file without using Maven plugins,
You can run the executable with [Remote Debug Flags](https://stackoverflow.com/questions/975271/remote-debugging-a-java-application)
and then attach IDE Debugger to it.
-To launch a development instance, after the above command, run:
+### Launching a development instance
+
+To launch a development instance, after [building the WAR file](#building-the-war-file), run:
```sh
-mvn -pl war jetty:run
+MAVEN_OPTS='--add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED' mvn -pl war jetty:run
```
(Beware that `maven-plugin` builds will not work in this mode, due to class loading conflicts.)
+### Running the Yarn frontend build
+
+To run the Yarn frontend build, after [building the WAR file](#building-the-war-file), add the downloaded versions of Node and Yarn to your path:
+
+```sh
+export PATH=$PWD/war/node:$PWD/war/node/yarn/dist/bin:$PATH
+```
+
+Then you can run Yarn with e.g.
+
+```sh
+cd war
+yarn
+```
+
### Building frontend assets
To work on the `war` module frontend assets, two processes are needed at the same time:
@@ -55,13 +72,14 @@ To work on the `war` module frontend assets, two processes are needed at the sam
On one terminal, start a development server that will not process frontend assets:
```sh
-mvn -pl war jetty:run -Dskip.yarn
+MAVEN_OPTS='--add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED' mvn -pl war jetty:run -Dskip.yarn
```
-On another terminal, move to the war folder and start a [webpack](https://webpack.js.org/) dev server:
+On another terminal, move to the `war` folder and start a [webpack](https://webpack.js.org/) dev server, after [adding Node and Yarn to your path](#running-the-yarn-frontend-build):
```sh
-cd war; yarn start
+cd war
+yarn start
```
### Gitpod
@@ -86,11 +104,24 @@ For linting we use a number of tools:
These are all configured to run as part of the Maven build, although they will be skipped if you are building with the `quick-build` profile.
-To automatically fix most issues run:
+To automatically fix backend issues, run:
-```bash
+```sh
mvn spotless:apply
-mvn -pl war frontend:yarn -Dfrontend.yarn.arguments=lint:fix
+```
+
+To view frontend issues, after [adding Node and Yarn to your path](#running-the-yarn-frontend-build), run:
+
+```sh
+cd war
+yarn lint
+```
+
+To fix frontend issues, after [adding Node and Yarn to your path](#running-the-yarn-frontend-build), run:
+
+```sh
+cd war
+yarn lint:fix
```
## Testing changes
@@ -111,14 +142,6 @@ In addition to the included tests, you can also find extra integration and UI
tests in the [Acceptance Test Harness (ATH)](https://github.com/jenkinsci/acceptance-test-harness) repository.
If you propose complex UI changes, you should create new ATH tests for them.
-### JavaScript unit tests
-
-In case there's only need to run the JS tests:
-
-```sh
-cd war; yarn test
-```
-
## Proposing Changes
The Jenkins project source code repositories are hosted at GitHub.
diff --git a/Jenkinsfile b/Jenkinsfile
index 8b87983f43fc..2380b3b66d9a 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -14,12 +14,12 @@ properties([
def axes = [
platforms: ['linux', 'windows'],
- jdks: [11, 17, 21],
+ jdks: [17, 21],
]
stage('Record build') {
retry(conditions: [kubernetesAgent(handleNonKubernetes: true), nonresumable()], count: 2) {
- node('maven-11') {
+ node('maven-17') {
infra.checkoutSCM()
/*
diff --git a/README.md b/README.md
index e41867cad4b9..e69e30927214 100644
--- a/README.md
+++ b/README.md
@@ -8,6 +8,7 @@
[![Jenkins LTS Release](https://img.shields.io/endpoint?url=https%3A%2F%2Fwww.jenkins.io%2Fchangelog-stable%2Fbadge.json)](https://www.jenkins.io/changelog-stable)
[![Docker Pulls](https://img.shields.io/docker/pulls/jenkins/jenkins.svg)](https://hub.docker.com/r/jenkins/jenkins/)
[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/3538/badge)](https://bestpractices.coreinfrastructure.org/projects/3538)
+[![Reproducible Builds](https://img.shields.io/badge/Reproducible_Builds-ok-green)](https://maven.apache.org/guides/mini/guide-reproducible-builds.html)
[![Gitter](https://img.shields.io/gitter/room/jenkinsci/jenkins)](https://app.gitter.im/#/room/#jenkinsci_jenkins:gitter.im)
In a nutshell, Jenkins is the leading open-source automation server.
diff --git a/ath.sh b/ath.sh
index e39c053ef66e..415a9c6bd72d 100644
--- a/ath.sh
+++ b/ath.sh
@@ -6,7 +6,7 @@ set -o xtrace
cd "$(dirname "$0")"
# https://github.com/jenkinsci/acceptance-test-harness/releases
-export ATH_VERSION=5814.vdc5d6d484b_40
+export ATH_VERSION=5941.v95f3439136c7
if [[ $# -eq 0 ]]; then
export JDK=17
diff --git a/bom/pom.xml b/bom/pom.xml
index 61dee6848809..ad23ed312b5e 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -38,9 +38,8 @@ THE SOFTWARE.
The module contains dependencies that are used by a specific Jenkins version
- 9.7
- 2.0.12
- 1843.ve7da_6a_9cf575
+ 2.0.0-M2
+ 1896.v8170998149d02.4.21
@@ -53,10 +52,17 @@ THE SOFTWARE.
pomimport
+
+ org.slf4j
+ slf4j-bom
+ 2.0.16
+ pom
+ import
+ org.springframeworkspring-framework-bom
- 5.3.33
+ 5.3.39pomimport
@@ -64,7 +70,7 @@ THE SOFTWARE.
org.springframework.securityspring-security-bom
- 5.8.11
+ 5.8.14pomimport
@@ -72,7 +78,7 @@ THE SOFTWARE.
args4jargs4j
- 2.33
+ 2.37com.github.spotbugs
@@ -82,7 +88,7 @@ THE SOFTWARE.
com.google.guavaguava
- 33.1.0-jre
+ 33.3.0-jre
@@ -113,18 +119,13 @@ THE SOFTWARE.
commons-codeccommons-codec
- 1.16.1
+ 1.17.1commons-collectionscommons-collections3.2.2
-
- commons-fileupload
- commons-fileupload
- 1.5
- commons-iocommons-io
@@ -150,6 +151,11 @@ THE SOFTWARE.
jenkins-stapler-support1.1
+
+ jakarta.servlet
+ jakarta.servlet-api
+ 4.0.4
+ jakarta.servlet.jsp.jstljakarta.servlet.jsp.jstl-api
@@ -190,6 +196,41 @@ THE SOFTWARE.
commons-compress1.26.1
+
+ org.apache.commons
+ commons-fileupload2
+ ${commons-fileupload2.version}
+
+
+ org.apache.commons
+ commons-fileupload2-core
+ ${commons-fileupload2.version}
+
+
+ org.apache.commons
+ commons-fileupload2-distribution
+ ${commons-fileupload2.version}
+
+
+ org.apache.commons
+ commons-fileupload2-jakarta-servlet5
+ ${commons-fileupload2.version}
+
+
+ org.apache.commons
+ commons-fileupload2-jakarta-servlet6
+ ${commons-fileupload2.version}
+
+
+ org.apache.commons
+ commons-fileupload2-javax
+ ${commons-fileupload2.version}
+
+
+ org.apache.commons
+ commons-fileupload2-portlet
+ ${commons-fileupload2.version}
+ org.codehaus.groovygroovy-all
@@ -254,7 +295,7 @@ THE SOFTWARE.
org.jvnet.hudsoncommons-jelly-tags-define
- 1.1-jenkins-20230713
+ 1.1-jenkins-20240510org.jvnet.localizer
@@ -269,7 +310,7 @@ THE SOFTWARE.
org.jvnet.winpwinp
- 1.30
+ 1.31org.kohsuke
@@ -294,7 +335,7 @@ THE SOFTWARE.
org.kohsuke.staplerjson-lib
- 2.4-jenkins-3
+ 2.4-jenkins-7org.kohsuke.stapler
@@ -311,62 +352,16 @@ THE SOFTWARE.
stapler-groovy${stapler.version}
-
- org.ow2.asm
- asm
- ${asm.version}
-
-
- org.ow2.asm
- asm-analysis
- ${asm.version}
-
-
- org.ow2.asm
- asm-commons
- ${asm.version}
-
-
- org.ow2.asm
- asm-tree
- ${asm.version}
-
-
- org.ow2.asm
- asm-util
- ${asm.version}
- org.samba.jcifsjcifs1.3.18-kohsuke-1
-
- org.slf4j
- jcl-over-slf4j
- ${slf4jVersion}
-
-
- org.slf4j
- log4j-over-slf4j
- ${slf4jVersion}
-
-
-
- org.slf4j
- slf4j-api
- ${slf4jVersion}
-
-
- org.slf4j
- slf4j-jdk14
- ${slf4jVersion}
- commons-loggingcommons-logging
- 1.3.1
+ 1.3.4provided
diff --git a/cli/pom.xml b/cli/pom.xml
index 342b4061016d..6309811a2e8b 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -15,7 +15,7 @@
https://github.com/jenkinsci/jenkins
- 2.12.1
+ 2.13.2
@@ -65,7 +65,7 @@
org.glassfish.tyrus.bundlestyrus-standalone-client-jdk
- 2.1.5
+ 2.2.0true
@@ -119,7 +119,7 @@
org.apache.maven.pluginsmaven-shade-plugin
- 3.5.2
+ 3.6.0
diff --git a/core/pom.xml b/core/pom.xml
index 3d1f16532c01..8e30ae211ed7 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -39,7 +39,9 @@ THE SOFTWARE.
https://github.com/jenkinsci/jenkins
- 2.9.1
+ 2.10.0
+
+ 3107.v665000b_51092
@@ -160,10 +162,6 @@ THE SOFTWARE.
commons-collectionscommons-collections
-
- commons-fileupload
- commons-fileupload
- commons-iocommons-io
@@ -284,6 +282,14 @@ THE SOFTWARE.
+
+ org.apache.commons
+ commons-fileupload2-core
+
+
+ org.apache.commons
+ commons-fileupload2-javax
+ org.codehaus.groovygroovy-all
@@ -409,26 +415,6 @@ THE SOFTWARE.
-
- org.ow2.asm
- asm
-
-
- org.ow2.asm
- asm-analysis
-
-
- org.ow2.asm
- asm-commons
-
-
- org.ow2.asm
- asm-tree
-
-
- org.ow2.asm
- asm-util
- org.slf4jjcl-over-slf4j
@@ -459,7 +445,6 @@ THE SOFTWARE.
jakarta.servletjakarta.servlet-api
- 4.0.4provided
@@ -524,6 +509,62 @@ THE SOFTWARE.
+
+ org.apache.maven.plugins
+ maven-enforcer-plugin
+
+
+
+ enforce-banned-dependencies
+
+ enforce
+
+
+
+
+
+
+ com.fasterxml.jackson.*
+ com.github.ben-manes.caffeine:caffeine
+ com.github.jnr:jnr-posix
+ com.github.mwiede:jsch
+ com.google.code.gson:gson
+ com.jayway.jsonpath:json-path
+ commons-httpclient:commons-httpclient
+ com.sun.activation:javax.activation
+ com.sun.mail:javax.mail
+ com.sun.xml.bind:jaxb-impl
+ io.jsonwebtoken
+
+ jakarta.activation:jakarta.activation-api:*:jar:compile
+ jakarta.activation:jakarta.activation-api:*:jar:runtime
+ jakarta.mail:jakarta.mail-api
+ javax.activation:javax.activation-api
+ javax.mail:javax.mail-api
+ javax.xml.bind:jaxb-api
+ joda-time:joda-time
+
+ net.bytebuddy:byte-buddy:*:jar:compile
+ net.bytebuddy:byte-buddy:*:jar:runtime
+ net.i2p.crypto:eddsa
+ net.minidev
+ org.apache.commons:commons-lang3
+ org.apache.commons:commons-text
+ org.apache.httpcomponents
+ org.bouncycastle
+ org.eclipse.angus:angus-activation
+ org.eclipse.angus:angus-mail
+ org.glassfish.jersey.*
+ org.json:json
+ org.ow2.asm
+ org.yaml:snakeyaml
+
+
+
+
+
+
+ org.codehaus.mojobuild-helper-maven-plugin
diff --git a/core/src/main/java/hudson/ClassicPluginStrategy.java b/core/src/main/java/hudson/ClassicPluginStrategy.java
index 10b0d179fdd7..3d6edf832f9f 100644
--- a/core/src/main/java/hudson/ClassicPluginStrategy.java
+++ b/core/src/main/java/hudson/ClassicPluginStrategy.java
@@ -52,6 +52,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.UUID;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
@@ -235,7 +236,11 @@ private static Manifest loadLinkedManifest(File archive) throws IOException {
dependencyLoader = getBaseClassLoader(atts, dependencyLoader);
return new PluginWrapper(pluginManager, archive, manifest, baseResourceURL,
- createClassLoader(paths, dependencyLoader, atts), disableFile, dependencies, optionalDependencies);
+ createClassLoader(computeClassLoaderName(manifest, archive), paths, dependencyLoader, atts), disableFile, dependencies, optionalDependencies);
+ }
+
+ private static String computeClassLoaderName(Manifest mf, File archive) {
+ return "PluginClassLoader for " + PluginWrapper.computeShortName(mf, archive.getName());
}
private void fix(Attributes atts, List optionalDependencies) {
@@ -247,7 +252,7 @@ private void fix(Attributes atts, List optionalDepende
for (Dependency d : DetachedPluginsUtil.getImpliedDependencies(pluginName, jenkinsVersion)) {
LOGGER.fine(() -> "implied dep " + pluginName + " → " + d.shortName);
- pluginManager.considerDetachedPlugin(d.shortName);
+ pluginManager.considerDetachedPlugin(d.shortName, pluginName);
optionalDependencies.add(d);
}
}
@@ -263,15 +268,28 @@ public static List getImpliedDependencies(String plugi
return DetachedPluginsUtil.getImpliedDependencies(pluginName, jenkinsVersion);
}
- @Deprecated
+ /**
+ * @deprecated since 2.459 use {@link #createClassLoader(String, List, ClassLoader, Attributes)}
+ */
+ @Deprecated(since = "2.459")
protected ClassLoader createClassLoader(List paths, ClassLoader parent) throws IOException {
return createClassLoader(paths, parent, null);
}
/**
- * Creates the classloader that can load all the specified jar files and delegate to the given parent.
+ * @deprecated since 2.459 use {@link #createClassLoader(String, List, ClassLoader, Attributes)}
*/
+ @Deprecated(since="2.459")
protected ClassLoader createClassLoader(List paths, ClassLoader parent, Attributes atts) throws IOException {
+ // generate a legacy id so at least we can track to something
+ return createClassLoader("unidentified-" + UUID.randomUUID(), paths, parent, atts);
+ }
+
+ /**
+ * Creates a classloader that can load all the specified jar files and delegate to the given parent.
+ * @since 2.459
+ */
+ protected ClassLoader createClassLoader(String name, List paths, ClassLoader parent, Attributes atts) throws IOException {
boolean usePluginFirstClassLoader =
atts != null && Boolean.parseBoolean(atts.getValue("PluginFirstClassLoader"));
@@ -285,9 +303,9 @@ protected ClassLoader createClassLoader(List paths, ClassLoader parent, At
}
URLClassLoader2 classLoader;
if (usePluginFirstClassLoader) {
- classLoader = new PluginFirstClassLoader2(urls.toArray(new URL[0]), parent);
+ classLoader = new PluginFirstClassLoader2(name, urls.toArray(new URL[0]), parent);
} else {
- classLoader = new URLClassLoader2(urls.toArray(new URL[0]), parent);
+ classLoader = new URLClassLoader2(name, urls.toArray(new URL[0]), parent);
}
return classLoader;
}
@@ -561,7 +579,7 @@ static final class DependencyClassLoader extends ClassLoader {
}
DependencyClassLoader(ClassLoader parent, File archive, List dependencies, PluginManager pluginManager) {
- super(parent);
+ super("dependency ClassLoader for " + archive.getPath(), parent);
this._for = archive;
this.dependencies = List.copyOf(dependencies);
this.pluginManager = pluginManager;
diff --git a/core/src/main/java/hudson/DependencyRunner.java b/core/src/main/java/hudson/DependencyRunner.java
index b7db91fb9416..f577440659cf 100644
--- a/core/src/main/java/hudson/DependencyRunner.java
+++ b/core/src/main/java/hudson/DependencyRunner.java
@@ -58,7 +58,7 @@ public void run() {
// Get all top-level projects
LOGGER.fine("assembling top level projects");
for (AbstractProject p : Jenkins.get().allItems(AbstractProject.class))
- if (p.getUpstreamProjects().size() == 0) {
+ if (p.getUpstreamProjects().isEmpty()) {
LOGGER.fine("adding top level project " + p.getName());
topLevelProjects.add(p);
} else {
diff --git a/core/src/main/java/hudson/EnvVars.java b/core/src/main/java/hudson/EnvVars.java
index 286151e46bf7..97def5f11a7f 100644
--- a/core/src/main/java/hudson/EnvVars.java
+++ b/core/src/main/java/hudson/EnvVars.java
@@ -114,8 +114,7 @@ public EnvVars(@NonNull Map m) {
// because of the backward compatibility, some parts of Jenkins passes
// EnvVars as Map so downcasting is safer.
- if (m instanceof EnvVars) {
- EnvVars lhs = (EnvVars) m;
+ if (m instanceof EnvVars lhs) {
this.platform = lhs.platform;
}
}
diff --git a/core/src/main/java/hudson/ExtensionFinder.java b/core/src/main/java/hudson/ExtensionFinder.java
index 801735835c0f..7301bcc39988 100644
--- a/core/src/main/java/hudson/ExtensionFinder.java
+++ b/core/src/main/java/hudson/ExtensionFinder.java
@@ -298,9 +298,11 @@ protected Injector resolve() {
}
private void refreshExtensionAnnotations() {
+ LOGGER.finer(() -> "refreshExtensionAnnotations()");
for (ExtensionComponent ec : moduleFinder.find(GuiceExtensionAnnotation.class, Hudson.getInstance())) {
GuiceExtensionAnnotation gea = ec.getInstance();
extensionAnnotations.put(gea.annotationType, gea);
+ LOGGER.finer(() -> "found " + gea.getClass());
}
}
@@ -328,6 +330,7 @@ public Injector getContainer() {
*/
@Override
public synchronized ExtensionComponentSet refresh() throws ExtensionRefreshException {
+ LOGGER.finer(() -> "refresh()");
refreshExtensionAnnotations();
// figure out newly discovered sezpoz components
List> delta = new ArrayList<>();
diff --git a/core/src/main/java/hudson/FilePath.java b/core/src/main/java/hudson/FilePath.java
index 3525b931ef78..06773dd9a9ee 100644
--- a/core/src/main/java/hudson/FilePath.java
+++ b/core/src/main/java/hudson/FilePath.java
@@ -31,8 +31,6 @@
import static hudson.Util.fixEmptyAndTrim;
import com.google.common.annotations.VisibleForTesting;
-import com.jcraft.jzlib.GZIPInputStream;
-import com.jcraft.jzlib.GZIPOutputStream;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
@@ -61,7 +59,6 @@
import hudson.util.ExceptionCatchingThreadFactory;
import hudson.util.FileVisitor;
import hudson.util.FormValidation;
-import hudson.util.HeadBufferingStream;
import hudson.util.IOUtils;
import hudson.util.NamingThreadFactory;
import hudson.util.io.Archiver;
@@ -80,6 +77,7 @@
import java.io.OutputStreamWriter;
import java.io.RandomAccessFile;
import java.io.Serializable;
+import java.io.UncheckedIOException;
import java.io.Writer;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
@@ -123,6 +121,8 @@
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
import jenkins.MasterToSlaveFileCallable;
import jenkins.SlaveToMasterFileCallable;
import jenkins.model.Jenkins;
@@ -130,14 +130,14 @@
import jenkins.util.ContextResettingExecutorService;
import jenkins.util.SystemProperties;
import jenkins.util.VirtualFile;
-import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
-import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
-import org.apache.commons.fileupload.FileItem;
+import org.apache.commons.fileupload2.core.FileItem;
import org.apache.commons.io.input.CountingInputStream;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.types.FileSet;
+import org.apache.tools.tar.TarEntry;
+import org.apache.tools.tar.TarInputStream;
import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipFile;
import org.jenkinsci.remoting.RoleChecker;
@@ -321,7 +321,7 @@ public static String normalize(@NonNull String path) {
buf.append(m.group(1));
path = path.substring(m.end());
}
- boolean isAbsolute = buf.length() > 0;
+ boolean isAbsolute = !buf.isEmpty();
// Split remaining path into tokens, trimming any duplicate or trailing separators
List tokens = new ArrayList<>();
int s = 0, end = path.length();
@@ -366,7 +366,7 @@ public static String normalize(@NonNull String path) {
}
// Recombine tokens
for (String token : tokens) buf.append(token);
- if (buf.length() == 0) buf.append('.');
+ if (buf.isEmpty()) buf.append('.');
return buf.toString();
}
@@ -887,15 +887,8 @@ public OutputStream compress(OutputStream out) {
},
GZIP {
@Override
- public InputStream extract(InputStream _in) throws IOException {
- HeadBufferingStream in = new HeadBufferingStream(_in, SIDE_BUFFER_SIZE);
- try {
- return new GZIPInputStream(in, 8192, true);
- } catch (IOException e) {
- // various people reported "java.io.IOException: Not in GZIP format" here, so diagnose this problem better
- in.fillSide();
- throw new IOException(e.getMessage() + "\nstream=" + Util.toHexString(in.getSideBuffer()), e);
- }
+ public InputStream extract(InputStream in) throws IOException {
+ return new GZIPInputStream(new BufferedInputStream(in));
}
@Override
@@ -1004,8 +997,7 @@ private boolean installIfNecessaryFrom(@NonNull URL archive, @NonNull TaskListen
}
}
- if (con instanceof HttpURLConnection) {
- HttpURLConnection httpCon = (HttpURLConnection) con;
+ if (con instanceof HttpURLConnection httpCon) {
int responseCode = httpCon.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_MOVED_PERM
|| responseCode == HttpURLConnection.HTTP_MOVED_TEMP) {
@@ -1166,7 +1158,9 @@ public void copyFrom(FilePath src) throws IOException, InterruptedException {
public void copyFrom(FileItem file) throws IOException, InterruptedException {
if (channel == null) {
try {
- file.write(new File(remote));
+ file.write(Paths.get(remote));
+ } catch (UncheckedIOException e) {
+ throw e.getCause();
} catch (IOException e) {
throw e;
} catch (Exception e) {
@@ -1180,6 +1174,14 @@ public void copyFrom(FileItem file) throws IOException, InterruptedException {
}
}
+ /**
+ * @deprecated use {@link #copyFrom(FileItem)}
+ */
+ @Deprecated
+ public void copyFrom(org.apache.commons.fileupload.FileItem file) throws IOException, InterruptedException {
+ copyFrom(file.toFileUpload2FileItem());
+ }
+
/**
* Code that gets executed on the machine where the {@link FilePath} is local.
* Used to act on {@link FilePath}.
@@ -1442,7 +1444,7 @@ private static class DeleteSuffixesRecursive extends MasterToSlaveFileCallable path.toFile());
+ Util.deleteRecursive(file.toPath(), Path::toFile);
}
}
@@ -1473,7 +1475,7 @@ private static class DeleteRecursive extends MasterToSlaveFileCallable {
@Override
public Void invoke(File f, VirtualChannel channel) throws IOException {
- Util.deleteRecursive(fileToPath(f), path -> path.toFile());
+ Util.deleteRecursive(fileToPath(f), Path::toFile);
return null;
}
}
@@ -1490,7 +1492,7 @@ private static class DeleteContents extends MasterToSlaveFileCallable {
@Override
public Void invoke(File f, VirtualChannel channel) throws IOException {
- Util.deleteContentsRecursive(fileToPath(f), path -> path.toFile());
+ Util.deleteContentsRecursive(fileToPath(f), Path::toFile);
return null;
}
}
@@ -2460,7 +2462,7 @@ private OffsetPipeSecureFileCallable(Pipe p, long offset) {
@Override
public Void invoke(File f, VirtualChannel channel) throws IOException {
try (OutputStream os = p.getOut();
- OutputStream out = new java.util.zip.GZIPOutputStream(os, 8192);
+ OutputStream out = new GZIPOutputStream(os, 8192);
RandomAccessFile raf = new RandomAccessFile(f, "r")) {
raf.seek(offset);
byte[] buf = new byte[8192];
@@ -3068,14 +3070,13 @@ private static void readFromTar(String name, File baseDir, InputStream in) throw
/**
* Reads from a tar stream and stores obtained files to the base dir.
- * Supports large files > 10 GB since 1.627 when this was migrated to use commons-compress.
+ * Supports large files > 10 GB since 1.627.
*/
private static void readFromTar(String name, File baseDir, InputStream in, Charset filenamesEncoding) throws IOException {
- // TarInputStream t = new TarInputStream(in);
- try (TarArchiveInputStream t = new TarArchiveInputStream(in, filenamesEncoding.name())) {
- TarArchiveEntry te;
- while ((te = t.getNextTarEntry()) != null) {
+ try (TarInputStream t = new TarInputStream(in, filenamesEncoding.name())) {
+ TarEntry te;
+ while ((te = t.getNextEntry()) != null) {
File f = new File(baseDir, te.getName());
if (!f.toPath().normalize().startsWith(baseDir.toPath())) {
throw new IOException(
diff --git a/core/src/main/java/hudson/Functions.java b/core/src/main/java/hudson/Functions.java
index 2e6abc978540..cd25f9bc9871 100644
--- a/core/src/main/java/hudson/Functions.java
+++ b/core/src/main/java/hudson/Functions.java
@@ -316,8 +316,7 @@ public static void initPageVariables(JellyContext context) {
*/
public static Class getTypeParameter(Class extends B> c, Class base, int n) {
Type parameterization = Types.getBaseClass(c, base);
- if (parameterization instanceof ParameterizedType) {
- ParameterizedType pt = (ParameterizedType) parameterization;
+ if (parameterization instanceof ParameterizedType pt) {
return Types.erasure(Types.getTypeArgument(pt, n));
} else {
throw new AssertionError(c + " doesn't properly parameterize " + base);
@@ -1422,7 +1421,7 @@ public static String getRelativeNameFrom(@CheckForNull Item p, @CheckForNull Ite
StringBuilder buf = new StringBuilder();
Item i = p;
while (true) {
- if (buf.length() > 0) buf.insert(0, separationString);
+ if (!buf.isEmpty()) buf.insert(0, separationString);
buf.insert(0, useDisplayName ? i.getDisplayName() : i.getName());
ItemGroup gr = i.getParent();
@@ -1873,7 +1872,7 @@ public static String joinPath(String... components) {
for (String s : components) {
if (s.isEmpty()) continue;
- if (buf.length() > 0) {
+ if (!buf.isEmpty()) {
if (buf.charAt(buf.length() - 1) != '/')
buf.append('/');
if (s.charAt(0) == '/') s = s.substring(1);
@@ -1979,8 +1978,7 @@ public String getServerName() {
@Deprecated
public String getCheckUrl(String userDefined, Object descriptor, String field) {
if (userDefined != null || field == null) return userDefined;
- if (descriptor instanceof Descriptor) {
- Descriptor d = (Descriptor) descriptor;
+ if (descriptor instanceof Descriptor d) {
return d.getCheckUrl(field);
}
return null;
@@ -1993,8 +1991,7 @@ public String getCheckUrl(String userDefined, Object descriptor, String field) {
public void calcCheckUrl(Map attributes, String userDefined, Object descriptor, String field) {
if (userDefined != null || field == null) return;
- if (descriptor instanceof Descriptor) {
- Descriptor d = (Descriptor) descriptor;
+ if (descriptor instanceof Descriptor d) {
CheckMethod m = d.getCheckMethod(field);
attributes.put("checkUrl", m.toStemUrl());
attributes.put("checkDependsOn", m.getDependsOn());
@@ -2057,7 +2054,7 @@ public static List> getCloudDescriptors() {
* Prepend a prefix only when there's the specified body.
*/
public String prepend(String prefix, String body) {
- if (body != null && body.length() > 0)
+ if (body != null && !body.isEmpty())
return prefix + body;
return body;
}
diff --git a/core/src/main/java/hudson/PluginFirstClassLoader2.java b/core/src/main/java/hudson/PluginFirstClassLoader2.java
index 974939d53acb..30618835c8a7 100644
--- a/core/src/main/java/hudson/PluginFirstClassLoader2.java
+++ b/core/src/main/java/hudson/PluginFirstClassLoader2.java
@@ -25,8 +25,9 @@ public class PluginFirstClassLoader2 extends URLClassLoader2 {
registerAsParallelCapable();
}
- public PluginFirstClassLoader2(@NonNull URL[] urls, @NonNull ClassLoader parent) {
- super(Objects.requireNonNull(urls), Objects.requireNonNull(parent));
+
+ public PluginFirstClassLoader2(String name, @NonNull URL[] urls, @NonNull ClassLoader parent) {
+ super(name, Objects.requireNonNull(urls), Objects.requireNonNull(parent));
}
/**
diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java
index d666fc207096..16c22e9ca90c 100644
--- a/core/src/main/java/hudson/PluginManager.java
+++ b/core/src/main/java/hudson/PluginManager.java
@@ -91,6 +91,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
@@ -138,10 +139,12 @@
import jenkins.util.xml.RestrictiveEntityResolver;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
-import org.apache.commons.fileupload.FileItem;
-import org.apache.commons.fileupload.FileUploadException;
-import org.apache.commons.fileupload.disk.DiskFileItemFactory;
-import org.apache.commons.fileupload.servlet.ServletFileUpload;
+import org.apache.commons.fileupload2.core.DiskFileItem;
+import org.apache.commons.fileupload2.core.DiskFileItemFactory;
+import org.apache.commons.fileupload2.core.FileItem;
+import org.apache.commons.fileupload2.core.FileUploadException;
+import org.apache.commons.fileupload2.javax.JavaxServletDiskFileUpload;
+import org.apache.commons.fileupload2.javax.JavaxServletFileUpload;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
@@ -612,7 +615,7 @@ public void run(Reactor reactor) throws Exception {
}});
}
- void considerDetachedPlugin(String shortName) {
+ void considerDetachedPlugin(String shortName, String source) {
if (new File(rootDir, shortName + ".jpi").isFile() ||
new File(rootDir, shortName + ".hpi").isFile() ||
new File(rootDir, shortName + ".jpl").isFile() ||
@@ -624,7 +627,7 @@ void considerDetachedPlugin(String shortName) {
for (String loadedFile : loadPluginsFromWar(getDetachedLocation(), (dir, name) -> normalisePluginName(name).equals(shortName))) {
String loaded = normalisePluginName(loadedFile);
File arc = new File(rootDir, loaded + ".jpi");
- LOGGER.info(() -> "Loading a detached plugin as a dependency: " + arc);
+ LOGGER.info(() -> "Loading a detached plugin " + arc + " as a dependency of " + source);
try {
plugins.add(strategy.createPluginWrapper(arc));
} catch (IOException e) {
@@ -713,6 +716,10 @@ protected static void addDependencies(URL hpiResUrl, String fromPath, Set d
}
Manifest manifest = parsePluginManifest(hpiResUrl);
+ if (manifest == null) {
+ return;
+ }
+
String dependencySpec = manifest.getMainAttributes().getValue("Plugin-Dependencies");
if (dependencySpec != null) {
String[] dependencyTokens = dependencySpec.split(",");
@@ -1098,7 +1105,7 @@ protected void copyBundledPlugin(URL src, String fileName) throws IOException {
}
/*package*/ static @CheckForNull Manifest parsePluginManifest(URL bundledJpi) {
- try (URLClassLoader cl = new URLClassLoader(new URL[]{bundledJpi})) {
+ try (URLClassLoader cl = new URLClassLoader("Temporary classloader for parsing " + bundledJpi.toString(), new URL[]{bundledJpi}, ClassLoader.getSystemClassLoader())) {
InputStream in = null;
try {
URL res = cl.findResource(PluginWrapper.MANIFEST_FILENAME);
@@ -1253,6 +1260,13 @@ public List getPlugins() {
return Collections.unmodifiableList(plugins);
}
+ @Restricted(NoExternalUse.class) // used by jelly
+ public List getPluginsSortedByTitle() {
+ return plugins.stream()
+ .sorted(Comparator.comparing(PluginWrapper::getDisplayName, String.CASE_INSENSITIVE_ORDER))
+ .collect(Collectors.toUnmodifiableList());
+ }
+
public List getFailedPlugins() {
return failedPlugins;
}
@@ -1435,13 +1449,13 @@ public HttpResponse doPluginsSearch(@QueryParameter String query, @QueryParamete
if (query == null || query.isBlank()) {
return true;
}
- return (plugin.name != null && plugin.name.toLowerCase().contains(query.toLowerCase())) ||
- (plugin.title != null && plugin.title.toLowerCase().contains(query.toLowerCase())) ||
- (plugin.excerpt != null && plugin.excerpt.toLowerCase().contains(query.toLowerCase())) ||
+ return (plugin.name != null && plugin.name.toLowerCase(Locale.ROOT).contains(query.toLowerCase(Locale.ROOT))) ||
+ (plugin.title != null && plugin.title.toLowerCase(Locale.ROOT).contains(query.toLowerCase(Locale.ROOT))) ||
+ (plugin.excerpt != null && plugin.excerpt.toLowerCase(Locale.ROOT).contains(query.toLowerCase(Locale.ROOT))) ||
plugin.hasCategory(query) ||
plugin.getCategoriesStream()
.map(UpdateCenter::getCategoryDisplayName)
- .anyMatch(category -> category != null && category.toLowerCase().contains(query.toLowerCase())) ||
+ .anyMatch(category -> category != null && category.toLowerCase(Locale.ROOT).contains(query.toLowerCase(Locale.ROOT))) ||
plugin.hasWarnings() && query.equalsIgnoreCase("warning:");
})
.limit(Math.max(limit - plugins.size(), 1))
@@ -1824,13 +1838,21 @@ static class FileUploadPluginCopier implements PluginCopier {
}
@Override
- public void copy(File target) throws Exception {
- fileItem.write(target);
+ public void copy(File target) throws IOException {
+ try {
+ fileItem.write(Util.fileToPath(target));
+ } catch (UncheckedIOException e) {
+ throw e.getCause();
+ }
}
@Override
public void cleanup() {
- fileItem.delete();
+ try {
+ fileItem.delete();
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
}
}
@@ -1865,8 +1887,8 @@ public HttpResponse doUploadPlugin(StaplerRequest req) throws IOException, Servl
String fileName = "";
PluginCopier copier;
File tmpDir = Files.createTempDirectory("uploadDir").toFile();
- ServletFileUpload upload = new ServletFileUpload(new DiskFileItemFactory(DiskFileItemFactory.DEFAULT_SIZE_THRESHOLD, tmpDir));
- List items = upload.parseRequest(req);
+ JavaxServletFileUpload upload = new JavaxServletDiskFileUpload(DiskFileItemFactory.builder().setFile(tmpDir).get());
+ List items = upload.parseRequest(req);
String string = items.get(1).getString();
if (string != null && !string.isBlank()) {
// this is a URL deployment
@@ -2329,7 +2351,7 @@ public static final class UberClassLoader extends ClassLoader {
}
public UberClassLoader(List activePlugins) {
- super(PluginManager.class.getClassLoader());
+ super("UberClassLoader", PluginManager.class.getClassLoader());
this.activePlugins = activePlugins;
}
@@ -2395,6 +2417,22 @@ public String toString() {
// only for debugging purpose
return "classLoader " + getClass().getName();
}
+
+ // TODO Remove this once we require post 2024-07 remoting minimum version and deleted ClassLoaderProxy#fetchJar(URL)
+ @SuppressFBWarnings(
+ value = "DMI_COLLECTION_OF_URLS",
+ justification = "All URLs point to local files, so no DNS lookup.")
+ @Restricted(NoExternalUse.class)
+ public boolean isPluginJar(URL jarUrl) {
+ for (PluginWrapper plugin : activePlugins) {
+ if (plugin.classLoader instanceof URLClassLoader) {
+ if (Set.of(((URLClassLoader) plugin.classLoader).getURLs()).contains(jarUrl)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
}
@SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console")
diff --git a/core/src/main/java/hudson/ProxyConfiguration.java b/core/src/main/java/hudson/ProxyConfiguration.java
index fa4206521ee6..cc5b30d956dc 100644
--- a/core/src/main/java/hudson/ProxyConfiguration.java
+++ b/core/src/main/java/hudson/ProxyConfiguration.java
@@ -540,6 +540,34 @@ public FormValidation doCheckPort(@QueryParameter String value) {
return FormValidation.ok();
}
+ /**
+ * Do check if the provided value is empty or composed of whitespaces.
+ * If so, return a validation warning.
+ *
+ * @param value the value to test
+ * @return a validation warning iff the provided value is empty or composed of whitespaces.
+ */
+ private static FormValidation checkProxyCredentials(String value) {
+ value = Util.fixEmptyAndTrim(value);
+ if (value == null) {
+ return FormValidation.ok();
+ } else {
+ return FormValidation.warning(Messages.ProxyConfiguration_NonTLSWarning());
+ }
+ }
+
+ @RequirePOST
+ @Restricted(NoExternalUse.class)
+ public FormValidation doCheckUserName(@QueryParameter String value) {
+ return checkProxyCredentials(value);
+ }
+
+ @RequirePOST
+ @Restricted(NoExternalUse.class)
+ public FormValidation doCheckSecretPassword(@QueryParameter String value) {
+ return checkProxyCredentials(value);
+ }
+
@RequirePOST
@Restricted(NoExternalUse.class)
public FormValidation doValidateProxy(
diff --git a/core/src/main/java/hudson/cli/CLIAction.java b/core/src/main/java/hudson/cli/CLIAction.java
index 4265a6283a2b..9e29b141560c 100644
--- a/core/src/main/java/hudson/cli/CLIAction.java
+++ b/core/src/main/java/hudson/cli/CLIAction.java
@@ -217,7 +217,7 @@ protected void closed(int statusCode, String reason) {
@Override
public Object getTarget() {
StaplerRequest req = Stapler.getCurrentRequest();
- if (req.getRestOfPath().length() == 0 && "POST".equals(req.getMethod())) {
+ if (req.getRestOfPath().isEmpty() && "POST".equals(req.getMethod())) {
// CLI connection request
if ("false".equals(req.getParameter("remoting"))) {
throw new PlainCliEndpointResponse();
diff --git a/core/src/main/java/hudson/cli/GroovyshCommand.java b/core/src/main/java/hudson/cli/GroovyshCommand.java
index 8bc4d522e254..18cbc6da7513 100644
--- a/core/src/main/java/hudson/cli/GroovyshCommand.java
+++ b/core/src/main/java/hudson/cli/GroovyshCommand.java
@@ -77,7 +77,7 @@ protected int run() {
StringBuilder commandLine = new StringBuilder();
for (String arg : args) {
- if (commandLine.length() > 0) {
+ if (!commandLine.isEmpty()) {
commandLine.append(" ");
}
commandLine.append(arg);
diff --git a/core/src/main/java/hudson/console/AnnotatedLargeText.java b/core/src/main/java/hudson/console/AnnotatedLargeText.java
index 8e0a59bee2af..4e0d3b9908af 100644
--- a/core/src/main/java/hudson/console/AnnotatedLargeText.java
+++ b/core/src/main/java/hudson/console/AnnotatedLargeText.java
@@ -28,8 +28,6 @@
import static java.lang.Math.abs;
-import com.jcraft.jzlib.GZIPInputStream;
-import com.jcraft.jzlib.GZIPOutputStream;
import edu.umd.cs.findbugs.annotations.CheckReturnValue;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.remoting.ObjectInputStreamEx;
@@ -45,6 +43,8 @@
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.concurrent.TimeUnit;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
diff --git a/core/src/main/java/hudson/console/ConsoleNote.java b/core/src/main/java/hudson/console/ConsoleNote.java
index 20ded06e9862..633a5ed54743 100644
--- a/core/src/main/java/hudson/console/ConsoleNote.java
+++ b/core/src/main/java/hudson/console/ConsoleNote.java
@@ -24,8 +24,6 @@
package hudson.console;
-import com.jcraft.jzlib.GZIPInputStream;
-import com.jcraft.jzlib.GZIPOutputStream;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.ExtensionPoint;
import hudson.Functions;
@@ -50,6 +48,8 @@
import java.util.Base64;
import java.util.Collection;
import java.util.List;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
import jenkins.model.Jenkins;
import jenkins.security.HMACConfidentialKey;
import jenkins.util.JenkinsJVM;
diff --git a/core/src/main/java/hudson/console/ExpandableDetailsNote.java b/core/src/main/java/hudson/console/ExpandableDetailsNote.java
index 1f558e6a260b..beb623b5034c 100644
--- a/core/src/main/java/hudson/console/ExpandableDetailsNote.java
+++ b/core/src/main/java/hudson/console/ExpandableDetailsNote.java
@@ -26,8 +26,8 @@
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
-import hudson.Functions;
import hudson.MarkupText;
+import hudson.Util;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -53,7 +53,8 @@ public ExpandableDetailsNote(String caption, String html) {
@Override
public ConsoleAnnotator annotate(Object context, MarkupText text, int charPos) {
text.addMarkup(charPos,
- "
" + html + "
");
+ "
" + html + "
");
return null;
}
diff --git a/core/src/main/java/hudson/console/HyperlinkNote.java b/core/src/main/java/hudson/console/HyperlinkNote.java
index 93e2ff40f493..1e582dab2b4a 100644
--- a/core/src/main/java/hudson/console/HyperlinkNote.java
+++ b/core/src/main/java/hudson/console/HyperlinkNote.java
@@ -88,10 +88,8 @@ static String encodeTo(String url, String text, BiFunction errors) {
buf.append(e.getClass().getSimpleName()).append(": ").append(e.getMessage());
}
}
- if (buf.length() == 0) return;
+ if (buf.isEmpty()) return;
Jenkins j = Jenkins.getInstanceOrNull();
if (j == null) { // Need this path, at least for unit tests, but also in case of very broken startup
// Startup failed, something is very broken, so report what we can.
diff --git a/core/src/main/java/hudson/init/impl/InstallUncaughtExceptionHandler.java b/core/src/main/java/hudson/init/impl/InstallUncaughtExceptionHandler.java
index 8873925847dc..c305e9a6febc 100644
--- a/core/src/main/java/hudson/init/impl/InstallUncaughtExceptionHandler.java
+++ b/core/src/main/java/hudson/init/impl/InstallUncaughtExceptionHandler.java
@@ -18,8 +18,8 @@
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
+import org.kohsuke.stapler.UncaughtExceptionFilter;
import org.kohsuke.stapler.WebApp;
-import org.kohsuke.stapler.compression.CompressionFilter;
/**
* Deals with exceptions that get thrown all the way up to the Stapler rendering layer.
@@ -30,7 +30,7 @@ public class InstallUncaughtExceptionHandler {
@Initializer
public static void init(final Jenkins j) throws IOException {
- CompressionFilter.setUncaughtExceptionHandler(j.servletContext, (e, context, req, rsp) -> handleException(j, e, req, rsp, 500));
+ UncaughtExceptionFilter.setUncaughtExceptionHandler(j.servletContext, (e, context, req, rsp) -> handleException(j, e, req, rsp, 500));
try {
Thread.setDefaultUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler());
LOGGER.log(Level.FINE, "Successfully installed a global UncaughtExceptionHandler.");
diff --git a/core/src/main/java/hudson/lifecycle/ExitLifecycle.java b/core/src/main/java/hudson/lifecycle/ExitLifecycle.java
index f8fcc3abefbf..038dafc442a3 100644
--- a/core/src/main/java/hudson/lifecycle/ExitLifecycle.java
+++ b/core/src/main/java/hudson/lifecycle/ExitLifecycle.java
@@ -26,6 +26,7 @@
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.Extension;
+import hudson.util.BootFailure;
import java.util.logging.Level;
import java.util.logging.Logger;
import jenkins.model.Jenkins;
@@ -72,4 +73,9 @@ public void restart() {
System.exit(exitOnRestart);
}
+
+ @Override
+ public void onBootFailure(BootFailure problem) {
+ restart();
+ }
}
diff --git a/core/src/main/java/hudson/lifecycle/Lifecycle.java b/core/src/main/java/hudson/lifecycle/Lifecycle.java
index dbc53d2b5005..028c3d1826b7 100644
--- a/core/src/main/java/hudson/lifecycle/Lifecycle.java
+++ b/core/src/main/java/hudson/lifecycle/Lifecycle.java
@@ -32,6 +32,8 @@
import hudson.Util;
import hudson.init.InitMilestone;
import hudson.init.Initializer;
+import hudson.util.BootFailure;
+import hudson.util.JenkinsReloadFailed;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
@@ -324,6 +326,14 @@ public boolean supportsDynamicLoad() {
return true;
}
+ /**
+ * Called when Jenkins has failed to boot.
+ * @param problem a boot failure (could be {@link JenkinsReloadFailed})
+ * @since 2.469
+ */
+ public void onBootFailure(BootFailure problem) {
+ }
+
@Restricted(NoExternalUse.class)
public static final class PlaceholderLifecycle extends ExitLifecycle {
diff --git a/core/src/main/java/hudson/logging/LogRecorder.java b/core/src/main/java/hudson/logging/LogRecorder.java
index 23ebc72cde6a..86aea6575048 100644
--- a/core/src/main/java/hudson/logging/LogRecorder.java
+++ b/core/src/main/java/hudson/logging/LogRecorder.java
@@ -393,7 +393,7 @@ private static final class SetLevel extends MasterToSlaveCallable {
void broadcast() {
for (Computer c : Jenkins.get().getComputers()) {
- if (c.getName().length() > 0) { // i.e. not master
+ if (!c.getName().isEmpty()) { // i.e. not master
VirtualChannel ch = c.getChannel();
if (ch != null) {
try {
@@ -595,7 +595,7 @@ public int compare(Computer c1, Computer c2) {
}
});
for (Computer c : Jenkins.get().getComputers()) {
- if (c.getName().length() == 0) {
+ if (c.getName().isEmpty()) {
continue; // master
}
List recs = new ArrayList<>();
diff --git a/core/src/main/java/hudson/model/AbstractBuild.java b/core/src/main/java/hudson/model/AbstractBuild.java
index bcd29082ebe1..85d62df78896 100644
--- a/core/src/main/java/hudson/model/AbstractBuild.java
+++ b/core/src/main/java/hudson/model/AbstractBuild.java
@@ -607,8 +607,7 @@ protected Launcher createLauncher(@NonNull BuildListener listener) throws IOExce
final Node currentNode = getCurrentNode();
Launcher l = currentNode.createLauncher(listener);
- if (project instanceof BuildableItemWithBuildWrappers) {
- BuildableItemWithBuildWrappers biwbw = (BuildableItemWithBuildWrappers) project;
+ if (project instanceof BuildableItemWithBuildWrappers biwbw) {
for (BuildWrapper bw : biwbw.getBuildWrappersList())
l = bw.decorateLauncher(AbstractBuild.this, l, listener);
}
diff --git a/core/src/main/java/hudson/model/AbstractItem.java b/core/src/main/java/hudson/model/AbstractItem.java
index 0649bdb33b10..adebec8f289f 100644
--- a/core/src/main/java/hudson/model/AbstractItem.java
+++ b/core/src/main/java/hudson/model/AbstractItem.java
@@ -25,7 +25,6 @@
package hudson.model;
-import static hudson.model.queue.Executables.getParentOf;
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
@@ -39,9 +38,6 @@
import hudson.cli.declarative.CLIResolver;
import hudson.model.listeners.ItemListener;
import hudson.model.listeners.SaveableListener;
-import hudson.model.queue.SubTask;
-import hudson.model.queue.Tasks;
-import hudson.model.queue.WorkUnit;
import hudson.security.ACL;
import hudson.security.ACLContext;
import hudson.security.AccessControlled;
@@ -57,12 +53,8 @@
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.Collection;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
import java.util.List;
import java.util.ListIterator;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
@@ -551,8 +543,7 @@ public final String getUrl() {
List ancestors = req.getAncestors();
if (!ancestors.isEmpty()) {
Ancestor last = ancestors.get(ancestors.size() - 1);
- if (last.getObject() instanceof View) {
- View view = (View) last.getObject();
+ if (last.getObject() instanceof View view) {
if (view.getOwner().getItemGroup() == getParent() && !view.isDefault()) {
// Showing something inside a view, so should use that as the base URL.
String prefix = req.getContextPath() + "/";
@@ -706,11 +697,13 @@ public void delete(StaplerRequest req, StaplerResponse rsp) throws IOException,
*
*
* Any exception indicates the deletion has failed, but {@link AbortException} would prevent the caller
- * from showing the stack trace. This
+ * from showing the stack trace.
+ * @see ItemDeletion
*/
@Override
public void delete() throws IOException, InterruptedException {
checkPermission(DELETE);
+ ItemListener.checkBeforeDelete(this);
boolean responsibleForAbortingBuilds = !ItemDeletion.contains(this);
boolean ownsRegistration = ItemDeletion.register(this);
if (!ownsRegistration && ItemDeletion.isRegistered(this)) {
@@ -720,87 +713,7 @@ public void delete() throws IOException, InterruptedException {
try {
// if a build is in progress. Cancel it.
if (responsibleForAbortingBuilds || ownsRegistration) {
- Queue queue = Queue.getInstance();
- if (this instanceof Queue.Task) {
- // clear any items in the queue so they do not get picked up
- queue.cancel((Queue.Task) this);
- }
- // now cancel any child items - this happens after ItemDeletion registration, so we can use a snapshot
- for (Queue.Item i : queue.getItems()) {
- Item item = Tasks.getItemOf(i.task);
- while (item != null) {
- if (item == this) {
- if (!queue.cancel(i)) {
- LOGGER.warning(() -> "failed to cancel " + i);
- }
- break;
- }
- if (item.getParent() instanceof Item) {
- item = (Item) item.getParent();
- } else {
- break;
- }
- }
- }
- // interrupt any builds in progress (and this should be a recursive test so that folders do not pay
- // the 15 second delay for every child item). This happens after queue cancellation, so will be
- // a complete set of builds in flight
- Map buildsInProgress = new LinkedHashMap<>();
- for (Computer c : Jenkins.get().getComputers()) {
- for (Executor e : c.getAllExecutors()) {
- final WorkUnit workUnit = e.getCurrentWorkUnit();
- final Queue.Executable executable = workUnit != null ? workUnit.getExecutable() : null;
- final SubTask subtask = executable != null ? getParentOf(executable) : null;
-
- if (subtask != null) {
- Item item = Tasks.getItemOf(subtask);
- while (item != null) {
- if (item == this) {
- buildsInProgress.put(e, e.getCurrentExecutable());
- e.interrupt(Result.ABORTED);
- break;
- }
- if (item.getParent() instanceof Item) {
- item = (Item) item.getParent();
- } else {
- break;
- }
- }
- }
- }
- }
- if (!buildsInProgress.isEmpty()) {
- // give them 15 seconds or so to respond to the interrupt
- long expiration = System.nanoTime() + TimeUnit.SECONDS.toNanos(15);
- // comparison with executor.getCurrentExecutable() == computation currently should always be true
- // as we no longer recycle Executors, but safer to future-proof in case we ever revisit recycling
- while (!buildsInProgress.isEmpty() && expiration - System.nanoTime() > 0L) {
- // we know that ItemDeletion will prevent any new builds in the queue
- // ItemDeletion happens-before Queue.cancel so we know that the Queue will stay clear
- // Queue.cancel happens-before collecting the buildsInProgress list
- // thus buildsInProgress contains the complete set we need to interrupt and wait for
- for (Iterator> iterator =
- buildsInProgress.entrySet().iterator();
- iterator.hasNext(); ) {
- Map.Entry entry = iterator.next();
- // comparison with executor.getCurrentExecutable() == executable currently should always be
- // true as we no longer recycle Executors, but safer to future-proof in case we ever
- // revisit recycling.
- if (!entry.getKey().isAlive()
- || entry.getValue() != entry.getKey().getCurrentExecutable()) {
- iterator.remove();
- }
- // I don't know why, but we have to keep interrupting
- entry.getKey().interrupt(Result.ABORTED);
- }
- Thread.sleep(50L);
- }
- if (!buildsInProgress.isEmpty()) {
- throw new Failure(Messages.AbstractItem_FailureToStopBuilds(
- buildsInProgress.size(), getFullDisplayName()
- ));
- }
- }
+ ItemDeletion.cancelBuildsInProgress(this);
}
if (this instanceof ItemGroup) {
// delete individual items first
@@ -937,6 +850,7 @@ public void updateByXml(Source source) throws IOException {
// if everything went well, commit this new version
out.commit();
SaveableListener.fireOnChange(this, getConfigFile());
+ ItemListener.fireOnUpdated(this);
} finally {
out.abort(); // don't leave anything behind
diff --git a/core/src/main/java/hudson/model/AbstractProject.java b/core/src/main/java/hudson/model/AbstractProject.java
index cceff9c42b19..917ff1f7b81f 100644
--- a/core/src/main/java/hudson/model/AbstractProject.java
+++ b/core/src/main/java/hudson/model/AbstractProject.java
@@ -77,7 +77,6 @@
import hudson.util.AlternativeUiTextProvider.Message;
import hudson.util.DescribableList;
import hudson.util.FormValidation;
-import hudson.widgets.HistoryWidget;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
@@ -519,8 +518,7 @@ private AbstractBuild getBuildForDeprecatedMethods() {
Executor e = Executor.currentExecutor();
if (e != null) {
Executable exe = e.getCurrentExecutable();
- if (exe instanceof AbstractBuild) {
- AbstractBuild b = (AbstractBuild) exe;
+ if (exe instanceof AbstractBuild b) {
if (b.getProject() == this)
return b;
}
@@ -1013,6 +1011,7 @@ public List getActions() {
* null if no information is available (for example,
* if no build was done yet.)
*/
+ @SuppressWarnings("deprecation")
@Override
public Node getLastBuiltOn() {
// where was it built on?
@@ -1721,11 +1720,6 @@ protected SearchIndexBuilder makeSearchIndex() {
return getParameterizedJobMixIn().extendSearchIndex(super.makeSearchIndex());
}
- @Override
- protected HistoryWidget createHistoryWidget() {
- return buildMixIn.createHistoryWidget();
- }
-
//
//
// actions
diff --git a/core/src/main/java/hudson/model/AdministrativeMonitor.java b/core/src/main/java/hudson/model/AdministrativeMonitor.java
index 7a9a0dde970c..bdbfb48027d3 100644
--- a/core/src/main/java/hudson/model/AdministrativeMonitor.java
+++ b/core/src/main/java/hudson/model/AdministrativeMonitor.java
@@ -183,7 +183,7 @@ public void doDisable(StaplerRequest req, StaplerResponse rsp) throws IOExceptio
/**
* Required permission to view this admin monitor.
- * By default {@link Jenkins#ADMINISTER}, but {@link Jenkins#SYSTEM_READ} is also supported.
+ * By default {@link Jenkins#ADMINISTER}, but {@link Jenkins#SYSTEM_READ} or {@link Jenkins#MANAGE} are also supported.
*
* Changing this permission check to return {@link Jenkins#SYSTEM_READ} will make the active
* administrative monitor appear on {@code manage.jelly} and on the globally visible
@@ -191,23 +191,69 @@ public void doDisable(StaplerRequest req, StaplerResponse rsp) throws IOExceptio
* {@link #doDisable(StaplerRequest, StaplerResponse)} will still always require Administer permission.
*
*
+ * This method only allows for a single permission to be returned. If more complex permission checks are required,
+ * override {@link #checkRequiredPermission()} and {@link #hasRequiredPermission()} instead.
+ *
+ *
* Implementers need to ensure that {@code doAct} and other web methods perform necessary permission checks:
* Users with System Read permissions are expected to be limited to read-only access.
* Form UI elements that change system state, e.g. toggling a feature on or off, need to be hidden from users
* lacking Administer permission.
*
+ * @since 2.233
+ * @deprecated Callers should use {@link #checkRequiredPermission()} or {@link #hasRequiredPermission()}.
*/
+ @Deprecated
public Permission getRequiredPermission() {
return Jenkins.ADMINISTER;
}
+ /**
+ * Checks if the current user has the minimum required permission to view this administrative monitor.
+ *
+ * Subclasses may override this method and {@link #hasRequiredPermission()} instead of {@link #getRequiredPermission()} to perform more complex permission checks,
+ * for example, checking either {@link Jenkins#MANAGE} or {@link Jenkins#SYSTEM_READ}.
+ *
+ * @see #getRequiredPermission()
+ * @see #hasRequiredPermission()
+ * @since 2.468
+ */
+ public void checkRequiredPermission() {
+ Jenkins.get().checkPermission(getRequiredPermission());
+ }
+
+ /**
+ * Checks if the current user has the minimum required permission to view this administrative monitor.
+ *
+ * Subclasses may override this method and {@link #checkRequiredPermission} instead of {@link #getRequiredPermission()} to perform more complex permission checks,
+ * for example, checking either {@link Jenkins#MANAGE} or {@link Jenkins#SYSTEM_READ}.
+ *
+ * @see #getRequiredPermission()
+ * @see #checkRequiredPermission()
+ * @since 2.468
+ */
+ public boolean hasRequiredPermission() {
+ return Jenkins.get().hasPermission(getRequiredPermission());
+ }
+
+ /**
+ * Checks if the current user has the minimum required permission to view any administrative monitor.
+ *
+ * @return true if the current user has the minimum required permission to view any administrative monitor.
+ *
+ * @since 2.468
+ */
+ public static boolean hasPermissionToDisplay() {
+ return Jenkins.get().hasAnyPermission(Jenkins.SYSTEM_READ, Jenkins.MANAGE);
+ }
+
/**
* Ensure that URLs in this administrative monitor are only accessible to users with {@link #getRequiredPermission()}.
*/
@Override
@Restricted(NoExternalUse.class)
public Object getTarget() {
- Jenkins.get().checkPermission(getRequiredPermission());
+ checkRequiredPermission();
return this;
}
diff --git a/core/src/main/java/hudson/model/Api.java b/core/src/main/java/hudson/model/Api.java
index 11db76c3581d..23e72072112f 100644
--- a/core/src/main/java/hudson/model/Api.java
+++ b/core/src/main/java/hudson/model/Api.java
@@ -190,8 +190,7 @@ public void doXml(StaplerRequest req, StaplerResponse rsp,
return;
}
- // switch to gzipped output
- try (OutputStream o = rsp.getCompressedOutputStream(req)) {
+ try (OutputStream o = rsp.getOutputStream()) {
if (isSimpleOutput(result)) {
// simple output allowed
rsp.setContentType("text/plain;charset=UTF-8");
diff --git a/core/src/main/java/hudson/model/AutoCompletionCandidates.java b/core/src/main/java/hudson/model/AutoCompletionCandidates.java
index 4e58860ceb89..d2f5f17c255f 100644
--- a/core/src/main/java/hudson/model/AutoCompletionCandidates.java
+++ b/core/src/main/java/hudson/model/AutoCompletionCandidates.java
@@ -32,6 +32,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Locale;
import javax.servlet.ServletException;
import jenkins.model.Jenkins;
import org.kohsuke.stapler.HttpResponse;
@@ -172,6 +173,6 @@ private String contextualNameOf(Item i) {
}
private static boolean startsWithImpl(String str, String prefix, boolean ignoreCase) {
- return ignoreCase ? str.toLowerCase().startsWith(prefix.toLowerCase()) : str.startsWith(prefix);
+ return ignoreCase ? str.toLowerCase(Locale.ROOT).startsWith(prefix.toLowerCase(Locale.ROOT)) : str.startsWith(prefix);
}
}
diff --git a/core/src/main/java/hudson/model/BooleanParameterDefinition.java b/core/src/main/java/hudson/model/BooleanParameterDefinition.java
index 5e270ef46da1..6e9db32216fd 100644
--- a/core/src/main/java/hudson/model/BooleanParameterDefinition.java
+++ b/core/src/main/java/hudson/model/BooleanParameterDefinition.java
@@ -59,8 +59,7 @@ public BooleanParameterDefinition(@NonNull String name, boolean defaultValue, @C
@Override
public ParameterDefinition copyWithDefaultValue(ParameterValue defaultValue) {
- if (defaultValue instanceof BooleanParameterValue) {
- BooleanParameterValue value = (BooleanParameterValue) defaultValue;
+ if (defaultValue instanceof BooleanParameterValue value) {
return new BooleanParameterDefinition(getName(), value.value, getDescription());
} else {
return this;
diff --git a/core/src/main/java/hudson/model/BuildAuthorizationToken.java b/core/src/main/java/hudson/model/BuildAuthorizationToken.java
index f101eb3d6e74..a09ed113e1cf 100644
--- a/core/src/main/java/hudson/model/BuildAuthorizationToken.java
+++ b/core/src/main/java/hudson/model/BuildAuthorizationToken.java
@@ -29,7 +29,6 @@
import hudson.security.ACL;
import java.io.IOException;
import javax.servlet.http.HttpServletResponse;
-import jenkins.security.ApiTokenProperty;
import org.kohsuke.stapler.HttpResponses;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
@@ -82,10 +81,6 @@ public static void checkPermission(Job, ?> project, BuildAuthorizationToken to
return;
}
- if (req.getAttribute(ApiTokenProperty.class.getName()) instanceof User) {
- return;
- }
-
rsp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
rsp.addHeader("Allow", "POST");
throw HttpResponses.forwardToView(project, "requirePOST.jelly");
diff --git a/core/src/main/java/hudson/model/Cause.java b/core/src/main/java/hudson/model/Cause.java
index 6990dd2b39e0..668587ffc8fc 100644
--- a/core/src/main/java/hudson/model/Cause.java
+++ b/core/src/main/java/hudson/model/Cause.java
@@ -182,6 +182,10 @@ public UpstreamCause(Run, ?> up) {
upstreamCauses = new ArrayList<>();
Set traversed = new HashSet<>();
for (Cause c : up.getCauses()) {
+ if (traversed.size() >= MAX_LEAF) {
+ upstreamCauses.add(new DeeplyNestedUpstreamCause());
+ break;
+ }
upstreamCauses.add(trim(c, MAX_DEPTH, traversed));
}
}
@@ -239,14 +243,16 @@ public int hashCode() {
}
UpstreamCause uc = (UpstreamCause) c;
List cs = new ArrayList<>();
- if (depth > 0) {
- if (traversed.add(uc.upstreamUrl + uc.upstreamBuild)) {
- for (Cause c2 : uc.upstreamCauses) {
- cs.add(trim(c2, depth - 1, traversed));
+ if (traversed.add(uc.upstreamUrl + uc.upstreamBuild)) {
+ for (Cause c2 : uc.upstreamCauses) {
+ if (depth <= 0 || traversed.size() >= MAX_LEAF) {
+ cs.add(new DeeplyNestedUpstreamCause());
+ break;
}
+ cs.add(trim(c2, depth - 1, traversed));
}
- } else if (traversed.size() < MAX_LEAF) {
- cs.add(new DeeplyNestedUpstreamCause());
+ } else {
+ traversed.add(uc.upstreamUrl + uc.upstreamBuild + '#' + traversed.size());
}
return new UpstreamCause(uc.upstreamProject, uc.upstreamBuild, uc.upstreamUrl, cs);
}
diff --git a/core/src/main/java/hudson/model/CauseAction.java b/core/src/main/java/hudson/model/CauseAction.java
index eca42ffb6999..316cf3a182d6 100644
--- a/core/src/main/java/hudson/model/CauseAction.java
+++ b/core/src/main/java/hudson/model/CauseAction.java
@@ -61,8 +61,7 @@ public CauseAction(Cause c) {
private void addCause(Cause c) {
synchronized (causeBag) {
- Integer cnt = causeBag.get(c);
- causeBag.put(c, cnt == null ? 1 : cnt + 1);
+ causeBag.compute(c, (unused, cnt) -> cnt == null ? 1 : cnt + 1);
}
}
diff --git a/core/src/main/java/hudson/model/Computer.java b/core/src/main/java/hudson/model/Computer.java
index 3470ad4d15c4..14a311381a37 100644
--- a/core/src/main/java/hudson/model/Computer.java
+++ b/core/src/main/java/hudson/model/Computer.java
@@ -1457,7 +1457,7 @@ public void doDumpExportTable(StaplerRequest req, StaplerResponse rsp) throws IO
checkPermission(Jenkins.ADMINISTER);
rsp.setContentType("text/plain");
- try (PrintWriter w = new PrintWriter(rsp.getCompressedWriter(req))) {
+ try (PrintWriter w = new PrintWriter(rsp.getWriter())) {
VirtualChannel vc = getChannel();
if (vc instanceof Channel) {
w.println("Controller to agent");
diff --git a/core/src/main/java/hudson/model/Descriptor.java b/core/src/main/java/hudson/model/Descriptor.java
index 817115c3ad17..b5eb07784d27 100644
--- a/core/src/main/java/hudson/model/Descriptor.java
+++ b/core/src/main/java/hudson/model/Descriptor.java
@@ -294,8 +294,7 @@ protected Descriptor() {
// detect an type error
Type bt = Types.getBaseClass(getClass(), Descriptor.class);
- if (bt instanceof ParameterizedType) {
- ParameterizedType pt = (ParameterizedType) bt;
+ if (bt instanceof ParameterizedType pt) {
// this 't' is the closest approximation of T of Descriptor.
Class t = Types.erasure(pt.getActualTypeArguments()[0]);
if (!t.isAssignableFrom(clazz))
@@ -595,6 +594,9 @@ public T newInstance(@Nullable StaplerRequest req, @NonNull JSONObject formData)
return verifyNewInstance(bindJSON(req, clazz, formData, true));
}
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException | RuntimeException e) {
+ if (e instanceof RuntimeException && e instanceof HttpResponse) {
+ throw (RuntimeException) e;
+ }
throw new LinkageError("Failed to instantiate " + clazz + " from " + RedactSecretJsonInErrorMessageSanitizer.INSTANCE.sanitize(formData), e);
}
}
@@ -675,7 +677,7 @@ public Object instantiate(Class actualType, JSONObject json) {
+ actualType.getName() + " " + json);
}
} catch (Exception x) {
- LOGGER.log(Level.WARNING, "falling back to default instantiation " + actualType.getName() + " " + json, x);
+ LOGGER.log(x instanceof HttpResponse ? Level.FINE : Level.WARNING, "falling back to default instantiation " + actualType.getName() + " " + json, x);
// If nested objects are not using newInstance, bindJSON will wind up throwing the same exception anyway,
// so logging above will result in a duplicated stack trace.
// However if they *are* then this is the only way to find errors in that newInstance.
@@ -688,8 +690,7 @@ public Object instantiate(Class actualType, JSONObject json) {
@Override
public Object onConvert(Type targetType, Class targetTypeErasure, Object jsonSource) {
- if (jsonSource instanceof JSONObject) {
- JSONObject json = (JSONObject) jsonSource;
+ if (jsonSource instanceof JSONObject json) {
if (isApplicable(targetTypeErasure, json)) {
LOGGER.log(Level.FINE, "switching to newInstance {0} {1}", new Object[] {targetTypeErasure.getName(), json});
try {
@@ -895,8 +896,7 @@ protected final String getViewPage(Class> clazz, String pageName) {
protected List getPossibleViewNames(String baseName) {
List names = new ArrayList<>();
for (Facet f : WebApp.get(Jenkins.get().servletContext).facets) {
- if (f instanceof JellyCompatibleFacet) {
- JellyCompatibleFacet jcf = (JellyCompatibleFacet) f;
+ if (f instanceof JellyCompatibleFacet jcf) {
for (String ext : jcf.getScriptExtensions())
names.add(baseName + ext);
}
@@ -1007,6 +1007,10 @@ public static URL getStaticHelpUrl(StaplerRequest req, Klass> c, String suffix
if (url != null) return url;
url = c.getResource(base + '_' + locale.getLanguage() + ".html");
if (url != null) return url;
+ if (locale.getLanguage().equals("en")) {
+ url = c.getResource(base + ".html");
+ if (url != null) return url;
+ }
}
// default
diff --git a/core/src/main/java/hudson/model/DirectoryBrowserSupport.java b/core/src/main/java/hudson/model/DirectoryBrowserSupport.java
index 8083efa72dbf..9d455f4c1bd3 100644
--- a/core/src/main/java/hudson/model/DirectoryBrowserSupport.java
+++ b/core/src/main/java/hudson/model/DirectoryBrowserSupport.java
@@ -33,6 +33,7 @@
import java.io.OutputStream;
import java.io.Serializable;
import java.net.URL;
+import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.LinkOption;
import java.nio.file.OpenOption;
@@ -90,11 +91,6 @@ public final class DirectoryBrowserSupport implements HttpResponse {
private static final Pattern TMPDIR_PATTERN = Pattern.compile(".+@tmp/.*");
- /**
- * Escape hatch for the protection against SECURITY-2481. If enabled, the absolute paths on Windows will be allowed.
- */
- static final String ALLOW_ABSOLUTE_PATH_PROPERTY_NAME = DirectoryBrowserSupport.class.getName() + ".allowAbsolutePath";
-
public final ModelObject owner;
public final String title;
@@ -230,7 +226,7 @@ private void serveFile(StaplerRequest req, StaplerResponse rsp, VirtualFile root
String pathElement = pathTokens.nextToken();
// Treat * and ? as wildcard unless they match a literal filename
if ((pathElement.contains("?") || pathElement.contains("*"))
- && inBase && !root.child((_base.length() > 0 ? _base + "/" : "") + pathElement).exists())
+ && inBase && !root.child((!_base.isEmpty() ? _base + "/" : "") + pathElement).exists())
inBase = false;
if (pathElement.equals("*zip*")) {
// the expected syntax is foo/bar/*zip*/bar.zip
@@ -245,7 +241,7 @@ private void serveFile(StaplerRequest req, StaplerResponse rsp, VirtualFile root
}
StringBuilder sb = inBase ? _base : _rest;
- if (sb.length() > 0) sb.append('/');
+ if (!sb.isEmpty()) sb.append('/');
sb.append(pathElement);
if (!inBase)
restSize++;
@@ -260,13 +256,11 @@ private void serveFile(StaplerRequest req, StaplerResponse rsp, VirtualFile root
if (base.isEmpty()) {
baseFile = root;
} else {
- if (!SystemProperties.getBoolean(ALLOW_ABSOLUTE_PATH_PROPERTY_NAME, false)) {
- boolean isAbsolute = root.run(new IsAbsolute(base));
- if (isAbsolute) {
- LOGGER.info(() -> "SECURITY-2481 The path provided in the URL (" + base + ") is absolute and thus is refused.");
- rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
- return;
- }
+ boolean isAbsolute = root.run(new IsAbsolute(base));
+ if (isAbsolute) {
+ LOGGER.info(() -> "SECURITY-2481 The path provided in the URL (" + base + ") is absolute and thus is refused.");
+ rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
+ return;
}
baseFile = root.child(base);
}
@@ -315,7 +309,7 @@ private void serveFile(StaplerRequest req, StaplerResponse rsp, VirtualFile root
}
List> glob = null;
- boolean patternUsed = rest.length() > 0;
+ boolean patternUsed = !rest.isEmpty();
boolean containsSymlink = false;
boolean containsTmpDir = false;
if (patternUsed) {
@@ -530,7 +524,7 @@ private static String createBackRef(int times) {
private static void zip(StaplerResponse rsp, VirtualFile root, VirtualFile dir, String glob) throws IOException, InterruptedException {
OutputStream outputStream = rsp.getOutputStream();
try (ZipOutputStream zos = new ZipOutputStream(outputStream)) {
- zos.setEncoding(System.getProperty("file.encoding")); // TODO JENKINS-20663 make this overridable via query parameter
+ zos.setEncoding(Charset.defaultCharset().displayName()); // TODO JENKINS-20663 make this overridable via query parameter
// TODO consider using run(Callable) here
if (glob.isEmpty()) {
diff --git a/core/src/main/java/hudson/model/DownloadService.java b/core/src/main/java/hudson/model/DownloadService.java
index b9a5610c4e35..0988b36785dd 100644
--- a/core/src/main/java/hudson/model/DownloadService.java
+++ b/core/src/main/java/hudson/model/DownloadService.java
@@ -404,7 +404,7 @@ public FormValidation updateNow() throws IOException {
}
jsonList.add(o);
}
- if (jsonList.size() == 0 && toolInstallerMetadataExists) {
+ if (jsonList.isEmpty() && toolInstallerMetadataExists) {
return FormValidation.warning("None of the tool installer metadata passed the signature check");
} else if (!toolInstallerMetadataExists) {
LOGGER.log(Level.WARNING, "No tool installer metadata found for " + id);
diff --git a/core/src/main/java/hudson/model/Executor.java b/core/src/main/java/hudson/model/Executor.java
index 0b40cc60eb18..020399bdd5b8 100644
--- a/core/src/main/java/hudson/model/Executor.java
+++ b/core/src/main/java/hudson/model/Executor.java
@@ -324,17 +324,19 @@ private void resetWorkUnit(String reason) {
@Override
public void run() {
- if (!owner.isOnline()) {
- resetWorkUnit("went off-line before the task's worker thread started");
- owner.removeExecutor(this);
- queue.scheduleMaintenance();
- return;
- }
- if (owner.getNode() == null) {
- resetWorkUnit("was removed before the task's worker thread started");
- owner.removeExecutor(this);
- queue.scheduleMaintenance();
- return;
+ if (!(owner instanceof Jenkins.MasterComputer)) {
+ if (!owner.isOnline()) {
+ resetWorkUnit("went off-line before the task's worker thread started");
+ owner.removeExecutor(this);
+ queue.scheduleMaintenance();
+ return;
+ }
+ if (owner.getNode() == null) {
+ resetWorkUnit("was removed before the task's worker thread started");
+ owner.removeExecutor(this);
+ queue.scheduleMaintenance();
+ return;
+ }
}
final WorkUnit workUnit;
lock.writeLock().lock();
@@ -352,13 +354,15 @@ public void run() {
task = Queue.withLock(new Callable<>() {
@Override
public SubTask call() throws Exception {
- if (!owner.isOnline()) {
- resetWorkUnit("went off-line before the task's worker thread was ready to execute");
- return null;
- }
- if (owner.getNode() == null) {
- resetWorkUnit("was removed before the task's worker thread was ready to execute");
- return null;
+ if (!(owner instanceof Jenkins.MasterComputer)) {
+ if (!owner.isOnline()) {
+ resetWorkUnit("went off-line before the task's worker thread was ready to execute");
+ return null;
+ }
+ if (owner.getNode() == null) {
+ resetWorkUnit("was removed before the task's worker thread was ready to execute");
+ return null;
+ }
}
// after this point we cannot unwind the assignment of the work unit, if the owner
// is removed or goes off-line then the build will just have to fail.
diff --git a/core/src/main/java/hudson/model/ExecutorListener.java b/core/src/main/java/hudson/model/ExecutorListener.java
index 6500e6acc2ec..beff34804d92 100644
--- a/core/src/main/java/hudson/model/ExecutorListener.java
+++ b/core/src/main/java/hudson/model/ExecutorListener.java
@@ -30,7 +30,7 @@
/**
* A listener for task related events from executors.
* A {@link Computer#getRetentionStrategy} or {@link SlaveComputer#getLauncher} may implement this interface.
- * Or you may create an implementation as an extension (since TODO).
+ * Or you may create an implementation as an extension (since 2.318).
* @author Stephen Connolly
* @since 1.312
*/
diff --git a/core/src/main/java/hudson/model/FileParameterDefinition.java b/core/src/main/java/hudson/model/FileParameterDefinition.java
index 343dd5831203..25cb08336da3 100644
--- a/core/src/main/java/hudson/model/FileParameterDefinition.java
+++ b/core/src/main/java/hudson/model/FileParameterDefinition.java
@@ -35,7 +35,7 @@
import java.util.Objects;
import javax.servlet.ServletException;
import net.sf.json.JSONObject;
-import org.apache.commons.fileupload.FileItem;
+import org.apache.commons.fileupload2.core.FileItem;
import org.apache.commons.io.FileUtils;
import org.jenkinsci.Symbol;
import org.kohsuke.stapler.DataBoundConstructor;
@@ -90,7 +90,7 @@ public String getHelpFile() {
public ParameterValue createValue(StaplerRequest req) {
FileItem src;
try {
- src = req.getFileItem(getName());
+ src = req.getFileItem2(getName());
} catch (ServletException | IOException e) {
// Not sure what to do here. We might want to raise this
// but that would involve changing the throws for this call
diff --git a/core/src/main/java/hudson/model/FileParameterValue.java b/core/src/main/java/hudson/model/FileParameterValue.java
index e9e30e63463b..343e30bb64f4 100644
--- a/core/src/main/java/hudson/model/FileParameterValue.java
+++ b/core/src/main/java/hudson/model/FileParameterValue.java
@@ -39,12 +39,13 @@
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.regex.Pattern;
import jenkins.util.SystemProperties;
-import org.apache.commons.fileupload.FileItem;
-import org.apache.commons.fileupload.FileItemHeaders;
-import org.apache.commons.fileupload.disk.DiskFileItem;
-import org.apache.commons.fileupload.util.FileItemHeadersImpl;
+import org.apache.commons.fileupload2.core.FileItem;
+import org.apache.commons.fileupload2.core.FileItemFactory;
+import org.apache.commons.fileupload2.core.FileItemHeaders;
+import org.apache.commons.fileupload2.core.FileItemHeadersProvider;
import org.apache.commons.io.FilenameUtils;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
@@ -55,12 +56,6 @@
/**
* {@link ParameterValue} for {@link FileParameterDefinition}.
*
- *
Persistence
- *
- * {@link DiskFileItem} is persistable via serialization,
- * (although the data may get very large in XML) so this object
- * as a whole is persistable.
- *
* @author Kohsuke Kawaguchi
*/
public class FileParameterValue extends ParameterValue {
@@ -97,8 +92,16 @@ public FileParameterValue(String name, FileItem file) {
this(name, file, FilenameUtils.getName(file.getName()));
}
+ /**
+ * @deprecated use {@link #FileParameterValue(String, FileItem)}
+ */
+ @Deprecated
+ public FileParameterValue(String name, org.apache.commons.fileupload.FileItem file) {
+ this(name, file.toFileUpload2FileItem(), FilenameUtils.getName(file.getName()));
+ }
+
public FileParameterValue(String name, File file, String originalFileName) {
- this(name, new FileItemImpl(file), originalFileName);
+ this(name, new FileItemImpl2(file), originalFileName);
}
protected FileParameterValue(String name, FileItem file, String originalFileName) {
@@ -146,10 +149,18 @@ public String getOriginalFileName() {
return originalFileName;
}
- public FileItem getFile() {
+ public FileItem getFile2() {
return file;
}
+ /**
+ * @deprecated use {@link #getFile2}
+ */
+ @Deprecated
+ public org.apache.commons.fileupload.FileItem getFile() {
+ return org.apache.commons.fileupload.FileItem.fromFileUpload2FileItem(getFile2());
+ }
+
@Override
public BuildWrapper createBuildWrapper(AbstractBuild, ?> build) {
return new BuildWrapper() {
@@ -248,11 +259,120 @@ private File getFileParameterFolderUnderBuild(AbstractBuild, ?> build) {
/**
* Default implementation from {@link File}.
+ *
+ * @deprecated use {@link FileItemImpl2}
*/
- public static final class FileItemImpl implements FileItem {
- private final File file;
+ @Deprecated
+ public static final class FileItemImpl implements org.apache.commons.fileupload.FileItem {
+ private final FileItem delegate;
public FileItemImpl(File file) {
+ if (file == null) {
+ throw new NullPointerException("file");
+ }
+ this.delegate = new FileItemImpl2(file);
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ return org.apache.commons.fileupload.FileItem.fromFileUpload2FileItem(delegate).getInputStream();
+ }
+
+ @Override
+ public String getContentType() {
+ return org.apache.commons.fileupload.FileItem.fromFileUpload2FileItem(delegate).getContentType();
+ }
+
+ @Override
+ @SuppressFBWarnings(value = "FILE_UPLOAD_FILENAME", justification = "for compatibility")
+ public String getName() {
+ return org.apache.commons.fileupload.FileItem.fromFileUpload2FileItem(delegate).getName();
+ }
+
+ @Override
+ public boolean isInMemory() {
+ return org.apache.commons.fileupload.FileItem.fromFileUpload2FileItem(delegate).isInMemory();
+ }
+
+ @Override
+ public long getSize() {
+ return org.apache.commons.fileupload.FileItem.fromFileUpload2FileItem(delegate).getSize();
+ }
+
+ @Override
+ public byte[] get() {
+ return org.apache.commons.fileupload.FileItem.fromFileUpload2FileItem(delegate).get();
+ }
+
+ @Override
+ public String getString(String encoding) throws UnsupportedEncodingException {
+ return org.apache.commons.fileupload.FileItem.fromFileUpload2FileItem(delegate).getString(encoding);
+ }
+
+ @Override
+ public String getString() {
+ return org.apache.commons.fileupload.FileItem.fromFileUpload2FileItem(delegate).getString();
+ }
+
+ @Override
+ public void write(File to) throws Exception {
+ org.apache.commons.fileupload.FileItem.fromFileUpload2FileItem(delegate).write(to);
+ }
+
+ @Override
+ public void delete() {
+ org.apache.commons.fileupload.FileItem.fromFileUpload2FileItem(delegate).delete();
+ }
+
+ @Override
+ public String getFieldName() {
+ return org.apache.commons.fileupload.FileItem.fromFileUpload2FileItem(delegate).getFieldName();
+ }
+
+ @Override
+ public void setFieldName(String name) {
+ org.apache.commons.fileupload.FileItem.fromFileUpload2FileItem(delegate).setFieldName(name);
+ }
+
+ @Override
+ public boolean isFormField() {
+ return org.apache.commons.fileupload.FileItem.fromFileUpload2FileItem(delegate).isFormField();
+ }
+
+ @Override
+ public void setFormField(boolean state) {
+ org.apache.commons.fileupload.FileItem.fromFileUpload2FileItem(delegate).setFormField(state);
+ }
+
+ @Override
+ @Deprecated
+ public OutputStream getOutputStream() throws IOException {
+ return org.apache.commons.fileupload.FileItem.fromFileUpload2FileItem(delegate).getOutputStream();
+ }
+
+ @Override
+ public org.apache.commons.fileupload.FileItemHeaders getHeaders() {
+ return org.apache.commons.fileupload.FileItem.fromFileUpload2FileItem(delegate).getHeaders();
+ }
+
+ @Override
+ public void setHeaders(org.apache.commons.fileupload.FileItemHeaders headers) {
+ org.apache.commons.fileupload.FileItem.fromFileUpload2FileItem(delegate).setHeaders(headers);
+ }
+
+ @Override
+ public FileItem toFileUpload2FileItem() {
+ return delegate;
+ }
+ }
+
+ /**
+ * Default implementation from {@link File}.
+ */
+ public static final class FileItemImpl2 implements FileItem {
+ private final File file;
+
+ public FileItemImpl2(File file) {
if (file == null) {
throw new NullPointerException("file");
}
@@ -294,8 +414,12 @@ public byte[] get() {
}
@Override
- public String getString(String encoding) throws UnsupportedEncodingException {
- return new String(get(), encoding);
+ public String getString(Charset toCharset) throws IOException {
+ try {
+ return new String(get(), toCharset);
+ } catch (UncheckedIOException e) {
+ throw e.getCause();
+ }
}
@Override
@@ -304,17 +428,19 @@ public String getString() {
}
@Override
- public void write(File to) throws Exception {
- new FilePath(file).copyTo(new FilePath(to));
+ public FileItem write(Path to) throws IOException {
+ try {
+ new FilePath(file).copyTo(new FilePath(to.toFile()));
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ return this;
}
@Override
- public void delete() {
- try {
- Files.deleteIfExists(file.toPath());
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
+ public FileItem delete() throws IOException {
+ Files.deleteIfExists(Util.fileToPath(file));
+ return this;
}
@Override
@@ -323,7 +449,8 @@ public String getFieldName() {
}
@Override
- public void setFieldName(String name) {
+ public FileItem setFieldName(String name) {
+ return this;
}
@Override
@@ -332,7 +459,8 @@ public boolean isFormField() {
}
@Override
- public void setFormField(boolean state) {
+ public FileItem setFormField(boolean state) {
+ return this;
}
@Override
@@ -343,11 +471,12 @@ public OutputStream getOutputStream() throws IOException {
@Override
public FileItemHeaders getHeaders() {
- return new FileItemHeadersImpl();
+ return FileItemFactory.AbstractFileItemBuilder.newFileItemHeaders();
}
@Override
- public void setHeaders(FileItemHeaders headers) {
+ public FileItemHeadersProvider setHeaders(FileItemHeaders headers) {
+ return this;
}
}
}
diff --git a/core/src/main/java/hudson/model/Fingerprint.java b/core/src/main/java/hudson/model/Fingerprint.java
index bd7721906b5f..df482e28e03c 100644
--- a/core/src/main/java/hudson/model/Fingerprint.java
+++ b/core/src/main/java/hudson/model/Fingerprint.java
@@ -608,7 +608,7 @@ public synchronized boolean removeAll(RangeSet that) {
public synchronized String toString() {
StringBuilder buf = new StringBuilder();
for (Range r : ranges) {
- if (buf.length() > 0) buf.append(',');
+ if (!buf.isEmpty()) buf.append(',');
buf.append(r);
}
return buf.toString();
@@ -787,7 +787,7 @@ public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingC
public static String serialize(RangeSet src) {
StringBuilder buf = new StringBuilder(src.ranges.size() * 10);
for (Range r : src.ranges) {
- if (buf.length() > 0) buf.append(',');
+ if (!buf.isEmpty()) buf.append(',');
if (r.isSingle())
buf.append(r.start);
else
diff --git a/core/src/main/java/hudson/model/FreeStyleProject.java b/core/src/main/java/hudson/model/FreeStyleProject.java
index c63a339d6611..5278d2a301bb 100644
--- a/core/src/main/java/hudson/model/FreeStyleProject.java
+++ b/core/src/main/java/hudson/model/FreeStyleProject.java
@@ -111,7 +111,7 @@ public String getIconFilePathPattern() {
@Override
public String getIconClassName() {
- return "icon-freestyle-project";
+ return "symbol-freestyle-project";
}
static {
diff --git a/core/src/main/java/hudson/model/ItemGroupMixIn.java b/core/src/main/java/hudson/model/ItemGroupMixIn.java
index 2a6b264db9de..faa214dee621 100644
--- a/core/src/main/java/hudson/model/ItemGroupMixIn.java
+++ b/core/src/main/java/hudson/model/ItemGroupMixIn.java
@@ -290,6 +290,7 @@ public synchronized TopLevelItem createProjectFromXML(String name, InputStream x
add(result);
+ result.onCreatedFromScratch();
ItemListener.fireOnCreated(result);
Jenkins.get().rebuildDependencyGraphAsync();
diff --git a/core/src/main/java/hudson/model/Items.java b/core/src/main/java/hudson/model/Items.java
index a1386565cfd0..fac4ae8a4a01 100644
--- a/core/src/main/java/hudson/model/Items.java
+++ b/core/src/main/java/hudson/model/Items.java
@@ -208,7 +208,7 @@ public static TopLevelItemDescriptor getDescriptor(String fqcn) {
public static String toNameList(Collection extends Item> items) {
StringBuilder buf = new StringBuilder();
for (Item item : items) {
- if (buf.length() > 0)
+ if (!buf.isEmpty())
buf.append(", ");
buf.append(item.getFullName());
}
diff --git a/core/src/main/java/hudson/model/Job.java b/core/src/main/java/hudson/model/Job.java
index 8c4e3183100a..4bd28ff2e325 100644
--- a/core/src/main/java/hudson/model/Job.java
+++ b/core/src/main/java/hudson/model/Job.java
@@ -266,8 +266,7 @@ public void onCopied(Item src, Item item) {
// If any of the other ItemListeners modify the job, they effect
// a save, which will clear the holdOffBuildUntilUserSave and
// causing a regression of JENKINS-2494
- if (item instanceof Job) {
- Job job = (Job) item;
+ if (item instanceof Job job) {
synchronized (job) {
job.holdOffBuildUntilUserSave = false;
}
@@ -626,11 +625,11 @@ public Collection> getOverrides() {
}
/**
- * @deprecated see {@link LazyBuildMixIn#createHistoryWidget()}
+ * @deprecated Remove any override, history widget is now created via {@link jenkins.widgets.WidgetFactory} implementation.
*/
@Deprecated(forRemoval = true, since = "2.410")
protected HistoryWidget createHistoryWidget() {
- return new HistoryWidget(this, getBuilds(), HISTORY_ADAPTER);
+ throw new IllegalStateException("HistoryWidget is now created via WidgetFactory implementation");
}
public static final HistoryWidget.Adapter HISTORY_ADAPTER = new Adapter<>() {
@@ -1110,8 +1109,7 @@ class FeedItem {
List entries = new ArrayList<>();
String scmDisplayName = "";
- if (this instanceof SCMTriggerItem) {
- SCMTriggerItem scmItem = (SCMTriggerItem) this;
+ if (this instanceof SCMTriggerItem scmItem) {
List scmNames = new ArrayList<>();
for (SCM s : scmItem.getSCMs()) {
scmNames.add(s.getDescriptor().getDisplayName());
diff --git a/core/src/main/java/hudson/model/Label.java b/core/src/main/java/hudson/model/Label.java
index b0c32f212a09..88661c6df690 100644
--- a/core/src/main/java/hudson/model/Label.java
+++ b/core/src/main/java/hudson/model/Label.java
@@ -454,7 +454,7 @@ public Api getApi() {
public abstract V accept(LabelVisitor visitor, P param);
/**
- * Lists up all the atoms contained in in this label.
+ * Lists all the atoms contained in this label.
*
* @since 1.420
*/
@@ -591,7 +591,7 @@ public Object unmarshal(HierarchicalStreamReader reader, final UnmarshallingCont
public static Set parse(@CheckForNull String labels) {
final Set r = new TreeSet<>();
labels = fixNull(labels);
- if (labels.length() > 0) {
+ if (!labels.isEmpty()) {
Jenkins j = Jenkins.get();
LabelAtom labelAtom = j.tryGetLabelAtom(labels);
if (labelAtom == null) {
diff --git a/core/src/main/java/hudson/model/ListView.java b/core/src/main/java/hudson/model/ListView.java
index 77852e994ece..d8f6a8d48bcb 100644
--- a/core/src/main/java/hudson/model/ListView.java
+++ b/core/src/main/java/hudson/model/ListView.java
@@ -359,7 +359,7 @@ public boolean isAddToCurrentView() {
private boolean needToAddToCurrentView(StaplerRequest req) throws ServletException {
String json = req.getParameter("json");
- if (json != null && json.length() > 0) {
+ if (json != null && !json.isEmpty()) {
// Submitted via UI
JSONObject form = req.getSubmittedForm();
return form.has("addToCurrentView") && form.getBoolean("addToCurrentView");
diff --git a/core/src/main/java/hudson/model/MyViewsProperty.java b/core/src/main/java/hudson/model/MyViewsProperty.java
index 9d7b8b651d1c..49fdfac48d2d 100644
--- a/core/src/main/java/hudson/model/MyViewsProperty.java
+++ b/core/src/main/java/hudson/model/MyViewsProperty.java
@@ -26,9 +26,11 @@
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.Extension;
import hudson.Util;
import hudson.model.Descriptor.FormException;
+import hudson.model.userproperty.UserPropertyCategory;
import hudson.security.ACL;
import hudson.util.FormValidation;
import hudson.views.MyViewsTabBar;
@@ -41,6 +43,7 @@
import java.util.concurrent.CopyOnWriteArrayList;
import javax.servlet.ServletException;
import jenkins.model.Jenkins;
+import jenkins.util.SystemProperties;
import net.sf.json.JSONObject;
import org.jenkinsci.Symbol;
import org.kohsuke.accmod.Restricted;
@@ -50,6 +53,7 @@
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerFallback;
+import org.kohsuke.stapler.StaplerProxy;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.verb.POST;
@@ -59,7 +63,14 @@
*
* @author Tom Huybrechts
*/
-public class MyViewsProperty extends UserProperty implements ModifiableViewGroup, Action, StaplerFallback {
+public class MyViewsProperty extends UserProperty implements ModifiableViewGroup, Action, StaplerFallback, StaplerProxy {
+
+ /**
+ * Escape hatch for StaplerProxy-based access control
+ */
+ @Restricted(NoExternalUse.class)
+ @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console")
+ public static /* non-final */ boolean SKIP_PERMISSION_CHECK = SystemProperties.getBoolean(MyViewsProperty.class.getName() + ".skipPermissionCheck");
/**
* Name of the primary view defined by the user.
@@ -225,7 +236,10 @@ public String getDisplayName() {
@Override
public String getIconFileName() {
- return "symbol-browsers";
+ if (SKIP_PERMISSION_CHECK || getACL().hasPermission(Jenkins.ADMINISTER))
+ return "symbol-browsers";
+ else
+ return null;
}
@Override
@@ -233,6 +247,14 @@ public String getUrlName() {
return "my-views";
}
+ @Override
+ public Object getTarget() {
+ if (!SKIP_PERMISSION_CHECK) {
+ checkPermission(Jenkins.ADMINISTER);
+ }
+ return this;
+ }
+
@Extension @Symbol("myView")
public static class DescriptorImpl extends UserPropertyDescriptor {
@@ -246,6 +268,11 @@ public String getDisplayName() {
public UserProperty newInstance(User user) {
return new MyViewsProperty();
}
+
+ @Override
+ public @NonNull UserPropertyCategory getUserPropertyCategory() {
+ return UserPropertyCategory.get(UserPropertyCategory.Preferences.class);
+ }
}
@Override
diff --git a/core/src/main/java/hudson/model/PaneStatusProperties.java b/core/src/main/java/hudson/model/PaneStatusProperties.java
index 29a460349440..4807020ca714 100644
--- a/core/src/main/java/hudson/model/PaneStatusProperties.java
+++ b/core/src/main/java/hudson/model/PaneStatusProperties.java
@@ -2,7 +2,9 @@
import static java.lang.String.format;
+import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
+import hudson.model.userproperty.UserPropertyCategory;
import hudson.util.PersistedList;
import java.io.IOException;
import javax.servlet.http.HttpSession;
@@ -56,6 +58,10 @@ public boolean isEnabled() {
return false;
}
+ @Override
+ public @NonNull UserPropertyCategory getUserPropertyCategory() {
+ return UserPropertyCategory.get(UserPropertyCategory.Invisible.class);
+ }
}
private static class PaneStatusPropertiesSessionFallback extends PaneStatusProperties {
diff --git a/core/src/main/java/hudson/model/Queue.java b/core/src/main/java/hudson/model/Queue.java
index d489f042100d..0d299fb9426d 100644
--- a/core/src/main/java/hudson/model/Queue.java
+++ b/core/src/main/java/hudson/model/Queue.java
@@ -130,6 +130,7 @@
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpResponses;
import org.kohsuke.stapler.QueryParameter;
+import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
import org.kohsuke.stapler.interceptor.RequirePOST;
@@ -2414,6 +2415,10 @@ public Api getApi() throws AccessDeniedException {
}
}
+ public HttpResponse doIndex(StaplerRequest req) {
+ return HttpResponses.text("Queue item exists. For details check, for example, " + req.getRequestURI() + "api/json?tree=cancelled,executable[url]");
+ }
+
protected Object readResolve() {
this.future = new FutureImpl(task);
return this;
diff --git a/core/src/main/java/hudson/model/Run.java b/core/src/main/java/hudson/model/Run.java
index a2061b162b0b..33658252b922 100644
--- a/core/src/main/java/hudson/model/Run.java
+++ b/core/src/main/java/hudson/model/Run.java
@@ -33,7 +33,6 @@
import static java.util.logging.Level.SEVERE;
import static java.util.logging.Level.WARNING;
-import com.jcraft.jzlib.GZIPInputStream;
import com.thoughtworks.xstream.XStream;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
@@ -106,6 +105,7 @@
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
+import java.util.zip.GZIPInputStream;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import jenkins.model.ArtifactManager;
@@ -1341,7 +1341,7 @@ public void computeDisplayName() {
private String combineLast(String[] token, int n) {
StringBuilder buf = new StringBuilder();
for (int i = Math.max(0, token.length - n); i < token.length; i++) {
- if (buf.length() > 0) buf.append('/');
+ if (!buf.isEmpty()) buf.append('/');
buf.append(token[i]);
}
return buf.toString();
@@ -2286,7 +2286,7 @@ public void doBuildTimestamp(StaplerRequest req, StaplerResponse rsp, @QueryPara
public void doConsoleText(StaplerRequest req, StaplerResponse rsp) throws IOException {
rsp.setContentType("text/plain;charset=UTF-8");
try (InputStream input = getLogInputStream();
- OutputStream os = rsp.getCompressedOutputStream(req);
+ OutputStream os = rsp.getOutputStream();
PlainTextConsoleOutputStream out = new PlainTextConsoleOutputStream(os)) {
IOUtils.copy(input, out);
}
diff --git a/core/src/main/java/hudson/model/TimeZoneProperty.java b/core/src/main/java/hudson/model/TimeZoneProperty.java
index bdf39c58527e..2675448dad80 100644
--- a/core/src/main/java/hudson/model/TimeZoneProperty.java
+++ b/core/src/main/java/hudson/model/TimeZoneProperty.java
@@ -4,6 +4,7 @@
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.Util;
+import hudson.model.userproperty.UserPropertyCategory;
import hudson.util.FormValidation;
import hudson.util.ListBoxModel;
import hudson.util.ListBoxModel.Option;
@@ -106,6 +107,10 @@ public FormValidation doCheckTimeZoneName(@QueryParameter String timeZoneName) {
}
}
+ @Override
+ public @NonNull UserPropertyCategory getUserPropertyCategory() {
+ return UserPropertyCategory.get(UserPropertyCategory.Account.class);
+ }
}
@CheckForNull
diff --git a/core/src/main/java/hudson/model/TopLevelItemDescriptor.java b/core/src/main/java/hudson/model/TopLevelItemDescriptor.java
index 0ba59421b0d9..d84e4a684f22 100644
--- a/core/src/main/java/hudson/model/TopLevelItemDescriptor.java
+++ b/core/src/main/java/hudson/model/TopLevelItemDescriptor.java
@@ -244,7 +244,7 @@ public String getIconClassName() {
// this one is easy... too easy... also will never happen
return IconSet.toNormalizedIconNameClass(path);
}
- if (Jenkins.RESOURCE_PATH.length() > 0 && path.startsWith(Jenkins.RESOURCE_PATH)) {
+ if (!Jenkins.RESOURCE_PATH.isEmpty() && path.startsWith(Jenkins.RESOURCE_PATH)) {
// will to live falling
path = path.substring(Jenkins.RESOURCE_PATH.length());
}
diff --git a/core/src/main/java/hudson/model/UpdateCenter.java b/core/src/main/java/hudson/model/UpdateCenter.java
index 56fd6e77c988..4df95b7b3f45 100644
--- a/core/src/main/java/hudson/model/UpdateCenter.java
+++ b/core/src/main/java/hudson/model/UpdateCenter.java
@@ -58,14 +58,18 @@
import hudson.util.PersistedList;
import hudson.util.VersionNumber;
import hudson.util.XStream2;
+import java.io.BufferedInputStream;
import java.io.File;
+import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.io.UncheckedIOException;
import java.lang.reflect.Constructor;
import java.net.HttpRetryException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
+import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.net.UnknownHostException;
@@ -86,6 +90,7 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;
@@ -390,7 +395,7 @@ public Badge getBadge() {
if (size > 0) {
StringBuilder tooltip = new StringBuilder();
Badge.Severity severity = Badge.Severity.WARNING;
- int securityFixSize = (int) plugins.stream().filter(plugin -> plugin.fixesSecurityVulnerabilities()).count();
+ int securityFixSize = (int) plugins.stream().filter(Plugin::fixesSecurityVulnerabilities).count();
int incompatibleSize = (int) plugins.stream().filter(plugin -> !plugin.isCompatibleWithInstalledVersion()).count();
if (size > 1) {
tooltip.append(jenkins.management.Messages.PluginsLink_updatesAvailable(size));
@@ -1322,6 +1327,10 @@ public File download(DownloadJob job, URL src) throws IOException {
sha512 != null ? new DigestOutputStream(_out, sha512) : _out, sha256) : _out, sha1) : _out;
InputStream in = con.getInputStream();
CountingInputStream cin = new CountingInputStream(in)) {
+ if (LOGGER.isLoggable(Level.FINE)) {
+ var sourceUrlString = getSourceUrl(src, con);
+ LOGGER.fine(() -> "Downloading " + job.getName() + " from " + sourceUrlString);
+ }
while ((len = cin.read(buf)) >= 0) {
out.write(buf, 0, len);
final int count = cin.getCount();
@@ -1358,15 +1367,22 @@ public File download(DownloadJob job, URL src) throws IOException {
return tmp;
} catch (IOException e) {
// assist troubleshooting in case of e.g. "too many redirects" by printing actual URL
- String extraMessage = "";
- if (con != null && con.getURL() != null && !src.toString().equals(con.getURL().toString())) {
- // Two URLs are considered equal if different hosts resolve to same IP. Prefer to log in case of string inequality,
- // because who knows how the server responds to different host name in the request header?
- // Also, since it involved name resolution, it'd be an expensive operation.
- extraMessage = " (redirected to: " + con.getURL() + ")";
+ throw new IOException("Failed to download from " + getSourceUrl(src, con), e);
+ }
+ }
+
+ private static String getSourceUrl(@NonNull URL src, @CheckForNull URLConnection connection) {
+ var sourceUrlString = src.toExternalForm();
+ if (connection != null) {
+ var connectionURL = connection.getURL();
+ if (connectionURL != null) {
+ var finalUrlString = connectionURL.toExternalForm();
+ if (!sourceUrlString.equals(finalUrlString)) {
+ return sourceUrlString + " → " + finalUrlString;
+ }
}
- throw new IOException("Failed to download from " + src + extraMessage, e);
}
+ return sourceUrlString;
}
/**
@@ -1798,6 +1814,83 @@ public void run() {
String getComputedSHA512();
}
+ @SuppressFBWarnings(value = "WEAK_MESSAGE_DIGEST_SHA1", justification = "SHA-1 is only used as a fallback if SHA-256/SHA-512 are not available")
+ private static class FileWithComputedChecksums implements WithComputedChecksums {
+
+ private final File file;
+
+ private String computedSHA1;
+ private String computedSHA256;
+ private String computedSHA512;
+
+ FileWithComputedChecksums(File file) {
+ this.file = Objects.requireNonNull(file);
+ }
+
+ @Override
+ public synchronized String getComputedSHA1() {
+ if (computedSHA1 != null) {
+ return computedSHA1;
+ }
+
+ MessageDigest messageDigest;
+ try {
+ messageDigest = MessageDigest.getInstance("SHA-1");
+ } catch (NoSuchAlgorithmException e) {
+ throw new UnsupportedOperationException(e);
+ }
+ computedSHA1 = computeDigest(messageDigest);
+ return computedSHA1;
+ }
+
+ @Override
+ public synchronized String getComputedSHA256() {
+ if (computedSHA256 != null) {
+ return computedSHA256;
+ }
+
+ MessageDigest messageDigest;
+ try {
+ messageDigest = MessageDigest.getInstance("SHA-256");
+ } catch (NoSuchAlgorithmException e) {
+ throw new UnsupportedOperationException(e);
+ }
+ computedSHA256 = computeDigest(messageDigest);
+ return computedSHA256;
+ }
+
+ @Override
+ public synchronized String getComputedSHA512() {
+ if (computedSHA512 != null) {
+ return computedSHA512;
+ }
+
+ MessageDigest messageDigest;
+ try {
+ messageDigest = MessageDigest.getInstance("SHA-512");
+ } catch (NoSuchAlgorithmException e) {
+ throw new UnsupportedOperationException(e);
+ }
+ computedSHA512 = computeDigest(messageDigest);
+ return computedSHA512;
+ }
+
+ private String computeDigest(MessageDigest digest) {
+ try (InputStream is = new FileInputStream(file);
+ BufferedInputStream bis = new BufferedInputStream(is)) {
+ byte[] buffer = new byte[1024];
+ int read = bis.read(buffer, 0, buffer.length);
+ while (read > -1) {
+ digest.update(buffer, 0, read);
+ read = bis.read(buffer, 0, buffer.length);
+ }
+ return Base64.getEncoder().encodeToString(digest.digest());
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+ }
+
/**
* Base class for a job that downloads a file from the Jenkins project.
*/
@@ -2234,7 +2327,24 @@ public void _run() throws IOException, InstallationStatus {
return;
}
try {
- super._run();
+ File cached = getCached(this);
+ if (cached != null) {
+ File dst = getDestination();
+
+ // A bit naive, but following the corresponding logic in UpdateCenterConfiguration#download...
+ File tmp = new File(dst.getPath() + ".tmp");
+ Files.copy(cached.toPath(), tmp.toPath(), StandardCopyOption.REPLACE_EXISTING);
+
+ config.postValidate(this, tmp);
+
+ /*
+ * Will unfortunately validate the checksum a second time, but this should still be faster than
+ * network I/O and at least allows us to reuse code...
+ */
+ config.install(this, tmp, dst);
+ } else {
+ super._run();
+ }
// if this is a bundled plugin, make sure it won't get overwritten
PluginWrapper pw = plugin.getInstalled();
@@ -2267,6 +2377,62 @@ public void _run() throws IOException, InstallationStatus {
}
}
+ /**
+ * If we happen to have the file already in the {@coode WEB-INF/detached-plugins} directory and it happens to
+ * match the checksum we were expecting, then save ourselves a trip to the download site. This method is
+ * best-effort, and if anything goes wrong we simply fall back to the standard download path.
+ *
+ * @return The cached file, or null for a cache miss
+ */
+ @CheckForNull
+ private File getCached(DownloadJob job) {
+ URL src;
+ try {
+ /*
+ * Could make PluginManager#getDetachedLocation public and consume it here, but this method is
+ * best-effort anyway.
+ */
+ src = Jenkins.get().servletContext.getResource(String.format("/WEB-INF/detached-plugins/%s.hpi", plugin.name));
+ } catch (MalformedURLException e) {
+ return null;
+ }
+
+ if (src == null || !"file".equals(src.getProtocol())) {
+ return null;
+ }
+
+ try {
+ config.preValidate(this, src);
+ } catch (IOException e) {
+ return null;
+ }
+
+ File cached;
+ try {
+ cached = new File(src.toURI());
+ } catch (URISyntaxException e) {
+ return null;
+ }
+
+ if (!cached.isFile()) {
+ return null;
+ }
+
+ WithComputedChecksums withComputedChecksums = new FileWithComputedChecksums(cached);
+ try {
+ verifyChecksums(withComputedChecksums, plugin, cached);
+ } catch (IOException | UncheckedIOException | UnsupportedOperationException e) {
+ return null;
+ }
+
+ // Allow us to reuse UpdateCenter.InstallationJob#replace.
+ job.computedSHA1 = withComputedChecksums.getComputedSHA1();
+ job.computedSHA256 = withComputedChecksums.getComputedSHA256();
+ job.computedSHA512 = withComputedChecksums.getComputedSHA512();
+
+ return cached;
+ }
+
/**
* Indicates there is another installation job for this plugin
* @since 2.1
diff --git a/core/src/main/java/hudson/model/UpdateSite.java b/core/src/main/java/hudson/model/UpdateSite.java
index 7488a9c85b57..8a38ef2d470b 100644
--- a/core/src/main/java/hudson/model/UpdateSite.java
+++ b/core/src/main/java/hudson/model/UpdateSite.java
@@ -192,7 +192,7 @@ public long getDataTimestamp() {
@Deprecated
public @CheckForNull Future updateDirectly(final boolean signatureCheck) {
if (! getDataFile().exists() || isDue()) {
- return Jenkins.get().getUpdateCenter().updateService.submit(new Callable() {
+ return Jenkins.get().getUpdateCenter().updateService.submit(new Callable<>() {
@Override public FormValidation call() throws Exception {
return updateDirectlyNow(signatureCheck);
}
diff --git a/core/src/main/java/hudson/model/UsageStatistics.java b/core/src/main/java/hudson/model/UsageStatistics.java
index 9fc052cd1009..341f135c52f1 100644
--- a/core/src/main/java/hudson/model/UsageStatistics.java
+++ b/core/src/main/java/hudson/model/UsageStatistics.java
@@ -26,7 +26,6 @@
import static java.util.concurrent.TimeUnit.DAYS;
-import com.jcraft.jzlib.GZIPOutputStream;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.Extension;
@@ -56,6 +55,7 @@
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
+import java.util.zip.GZIPOutputStream;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
diff --git a/core/src/main/java/hudson/model/User.java b/core/src/main/java/hudson/model/User.java
index a242385cb11a..792622eb3c54 100644
--- a/core/src/main/java/hudson/model/User.java
+++ b/core/src/main/java/hudson/model/User.java
@@ -39,13 +39,11 @@
import hudson.XmlFile;
import hudson.init.InitMilestone;
import hudson.init.Initializer;
-import hudson.model.Descriptor.FormException;
import hudson.model.listeners.SaveableListener;
import hudson.security.ACL;
import hudson.security.AccessControlled;
import hudson.security.SecurityRealm;
import hudson.security.UserMayOrMayNotExistException2;
-import hudson.util.FormApply;
import hudson.util.FormValidation;
import hudson.util.RunList;
import hudson.util.XStream2;
@@ -77,7 +75,6 @@
import jenkins.security.LastGrantedAuthoritiesProperty;
import jenkins.security.UserDetailsCache;
import jenkins.util.SystemProperties;
-import net.sf.json.JSONObject;
import org.jenkinsci.Symbol;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
@@ -87,7 +84,6 @@
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
import org.kohsuke.stapler.interceptor.RequirePOST;
-import org.kohsuke.stapler.verb.POST;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
@@ -342,6 +338,29 @@ public synchronized void addProperty(@NonNull UserProperty p) throws IOException
save();
}
+ /**
+ * Expand {@link #addProperty(UserProperty)} for multiple properties to be done at once.
+ * Expected to be used by the categorized configuration pages to update part of the properties.
+ * The properties not included in the list will be let untouched.
+ * It will call the {@link UserProperty#setUser(User)} method and at the end, {@link #save()} once.
+ *
+ * @since 2.468
+ */
+ public synchronized void addProperties(@NonNull List multipleProperties) throws IOException {
+ List newProperties = new ArrayList<>(this.properties);
+ for (UserProperty property : multipleProperties) {
+ UserProperty oldProp = getProperty(property.getClass());
+ if (oldProp != null) {
+ newProperties.remove(oldProp);
+ }
+ newProperties.add(property);
+ property.setUser(this);
+ }
+
+ this.properties = newProperties;
+ this.save();
+ }
+
/**
* List of all {@link UserProperty}s exposed primarily for the remoting API.
*/
@@ -859,48 +878,6 @@ public Api getApi() {
return new Api(this);
}
- /**
- * Accepts submission from the configuration page.
- */
- @POST
- public void doConfigSubmit(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, FormException {
- checkPermission(Jenkins.ADMINISTER);
-
- JSONObject json = req.getSubmittedForm();
- String oldFullName = this.fullName;
- fullName = json.getString("fullName");
- description = json.getString("description");
-
- List props = new ArrayList<>();
- int i = 0;
- for (UserPropertyDescriptor d : UserProperty.all()) {
- UserProperty p = getProperty(d.clazz);
-
- JSONObject o = json.optJSONObject("userProperty" + i++);
- if (o != null) {
- if (p != null) {
- p = p.reconfigure(req, o);
- } else {
- p = d.newInstance(req, o);
- }
- }
-
- if (p != null) {
- p.setUser(this);
- props.add(p);
- }
- }
- this.properties = props;
-
- save();
-
- if (oldFullName != null && !oldFullName.equals(this.fullName)) {
- UserDetailsCache.get().invalidate(oldFullName);
- }
-
- FormApply.success(".").generateResponse(req, rsp, this);
- }
-
/**
* Deletes this user from Hudson.
*/
diff --git a/core/src/main/java/hudson/model/UserProperty.java b/core/src/main/java/hudson/model/UserProperty.java
index a9b9dbae7acd..a6ebeb738b23 100644
--- a/core/src/main/java/hudson/model/UserProperty.java
+++ b/core/src/main/java/hudson/model/UserProperty.java
@@ -24,9 +24,13 @@
package hudson.model;
+import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.DescriptorExtensionList;
import hudson.ExtensionPoint;
import hudson.model.Descriptor.FormException;
+import hudson.model.userproperty.UserPropertyCategory;
+import java.util.ArrayList;
+import java.util.List;
import jenkins.model.Jenkins;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.StaplerRequest;
@@ -58,6 +62,10 @@ public abstract class UserProperty implements ReconfigurableDescribable all(
return Jenkins.get().getDescriptorList(UserProperty.class);
}
+ /**
+ * Returns all the registered {@link UserPropertyCategory} descriptors for a given category.
+ *
+ * @since 2.468
+ */
+ public static List allByCategoryClass(@NonNull Class extends UserPropertyCategory> categoryClass) {
+ DescriptorExtensionList all = all();
+
+ List onlyForTheCategory = new ArrayList<>(all.size());
+ for (UserPropertyDescriptor descriptor : all) {
+ if (descriptor.getUserPropertyCategory().getClass().equals(categoryClass)) {
+ onlyForTheCategory.add(descriptor);
+ }
+ }
+
+ return onlyForTheCategory;
+ }
+
@Override
public UserProperty reconfigure(StaplerRequest req, JSONObject form) throws FormException {
return form == null ? null : getDescriptor().newInstance(req, form);
diff --git a/core/src/main/java/hudson/model/UserPropertyDescriptor.java b/core/src/main/java/hudson/model/UserPropertyDescriptor.java
index 22e05ea59aff..66762bf3c716 100644
--- a/core/src/main/java/hudson/model/UserPropertyDescriptor.java
+++ b/core/src/main/java/hudson/model/UserPropertyDescriptor.java
@@ -24,6 +24,12 @@
package hudson.model;
+import edu.umd.cs.findbugs.annotations.CheckForNull;
+import edu.umd.cs.findbugs.annotations.NonNull;
+import hudson.model.userproperty.UserPropertyCategory;
+import java.util.Optional;
+import org.jenkinsci.Symbol;
+
/**
* {@link Descriptor} for {@link UserProperty}.
*
@@ -73,4 +79,51 @@ protected UserPropertyDescriptor() {
public boolean isEnabled() {
return true;
}
+
+ /**
+ * Define the category for this user property descriptor.
+ *
+ * @return never null, always the same value for a given instance of {@link Descriptor}.
+ *
+ * @since 2.468
+ */
+ public @NonNull UserPropertyCategory getUserPropertyCategory() {
+ // As this method is expected to be overloaded by subclasses
+ // the logic here is just done to support plugins with older core version
+ String categoryAsString = this.getUserPropertyCategoryAsString();
+ if (categoryAsString != null) {
+ Optional firstIfFound = UserPropertyCategory.all().stream()
+ .filter(cat -> {
+ Symbol symbolAnnotation = cat.getClass().getAnnotation(Symbol.class);
+ if (symbolAnnotation != null) {
+ for (String symbolValue : symbolAnnotation.value()) {
+ if (symbolValue.equalsIgnoreCase(categoryAsString)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ })
+ .findFirst();
+ if (firstIfFound.isPresent()) {
+ return firstIfFound.get();
+ }
+ }
+ return UserPropertyCategory.get(UserPropertyCategory.Unclassified.class);
+ }
+
+ /**
+ * Method proposed to prevent plugins to rely on too recent core version
+ * while keeping the possibility to use the categories.
+ *
+ * @deprecated This should only be used when the core requirement is below the version this method was added
+ *
+ * @return String name corresponding to the symbol of {@link #getUserPropertyCategory()}
+ *
+ * @since 2.468
+ */
+ @Deprecated
+ protected @CheckForNull String getUserPropertyCategoryAsString() {
+ return null;
+ }
}
diff --git a/core/src/main/java/hudson/model/View.java b/core/src/main/java/hudson/model/View.java
index 0df916a19b5a..7b1bd98eb9ee 100644
--- a/core/src/main/java/hudson/model/View.java
+++ b/core/src/main/java/hudson/model/View.java
@@ -98,6 +98,8 @@
import net.sf.json.JSONObject;
import org.jenkins.ui.icon.Icon;
import org.jenkins.ui.icon.IconSet;
+import org.jenkins.ui.symbol.Symbol;
+import org.jenkins.ui.symbol.SymbolRequest;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
import org.kohsuke.stapler.DataBoundSetter;
@@ -508,8 +510,7 @@ private boolean filterQueueItemTest(Queue.Item item, Collection vi
}
}
// Check root project for sub-job projects (e.g. matrix jobs).
- if (item.task instanceof AbstractProject, ?>) {
- AbstractProject, ?> project = (AbstractProject, ?>) item.task;
+ if (item.task instanceof AbstractProject, ?> project) {
return viewItems.contains(project.getRootProject());
}
return false;
@@ -801,11 +802,20 @@ public Categories doItemCategories(StaplerRequest req, StaplerResponse rsp, @Que
String iconClassName = descriptor.getIconClassName();
if (iconClassName != null && !iconClassName.isBlank()) {
metadata.put("iconClassName", iconClassName);
- if (resUrl != null) {
- Icon icon = IconSet.icons
- .getIconByClassSpec(String.join(" ", iconClassName, iconStyle));
- if (icon != null) {
- metadata.put("iconQualifiedUrl", icon.getQualifiedUrl(resUrl));
+ if (iconClassName.startsWith("symbol-")) {
+ String iconXml = Symbol.get(new SymbolRequest.Builder()
+ .withName(iconClassName.split(" ")[0].substring(7))
+ .withPluginName(Functions.extractPluginNameFromIconSrc(iconClassName))
+ .withClasses("icon-xlg")
+ .build());
+ metadata.put("iconXml", iconXml);
+ } else {
+ if (resUrl != null) {
+ Icon icon = IconSet.icons
+ .getIconByClassSpec(String.join(" ", iconClassName, iconStyle));
+ if (icon != null) {
+ metadata.put("iconQualifiedUrl", icon.getQualifiedUrl(resUrl));
+ }
}
}
}
@@ -844,8 +854,7 @@ public BuildTimelineWidget getTimeline() {
public void doRssLatest(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
List lastBuilds = new ArrayList<>();
for (TopLevelItem item : getItems()) {
- if (item instanceof Job) {
- Job job = (Job) item;
+ if (item instanceof Job job) {
Run lb = job.getLastBuild();
if (lb != null) lastBuilds.add(lb);
}
diff --git a/core/src/main/java/hudson/model/WorkspaceCleanupThread.java b/core/src/main/java/hudson/model/WorkspaceCleanupThread.java
index 00ef65c4a92e..8159f1f20cc1 100644
--- a/core/src/main/java/hudson/model/WorkspaceCleanupThread.java
+++ b/core/src/main/java/hudson/model/WorkspaceCleanupThread.java
@@ -24,6 +24,8 @@
package hudson.model;
+import static hudson.Util.fileToPath;
+
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.Extension;
@@ -31,12 +33,19 @@
import hudson.FilePath;
import hudson.Functions;
import hudson.Util;
+import hudson.remoting.VirtualChannel;
+import hudson.slaves.WorkspaceList;
+import java.io.File;
+import java.io.FileFilter;
import java.io.IOException;
+import java.io.Serializable;
+import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
+import jenkins.MasterToSlaveFileCallable;
import jenkins.model.Jenkins;
import jenkins.model.ModifiableTopLevelItemGroup;
import jenkins.util.SystemProperties;
@@ -90,8 +99,7 @@ public static void invoke() {
if (check) {
listener.getLogger().println("Deleting " + ws + " on " + node.getDisplayName());
try {
- ws.deleteSuffixesRecursive();
- ws.deleteRecursive();
+ ws.act(new CleanupOldWorkspaces(retainForDays));
} catch (IOException | InterruptedException x) {
Functions.printStackTrace(x, listener.error("Failed to delete " + ws + " on " + node.getDisplayName()));
}
@@ -101,21 +109,6 @@ public static void invoke() {
}
private boolean shouldBeDeleted(@NonNull TopLevelItem item, FilePath dir, @NonNull Node n) throws IOException, InterruptedException {
- // TODO: the use of remoting is not optimal.
- // One remoting can execute "exists", "lastModified", and "delete" all at once.
- // (Could even invert master loop so that one FileCallable takes care of all known items.)
- if (!dir.exists()) {
- LOGGER.log(Level.FINE, "Directory {0} does not exist", dir);
- return false;
- }
-
- // if younger than a month, keep it
- long now = new Date().getTime();
- if (dir.lastModified() + retainForDays * DAY > now) {
- LOGGER.log(Level.FINE, "Directory {0} is only {1} old, so not deleting", new Object[] {dir, Util.getTimeSpanString(now - dir.lastModified())});
- return false;
- }
-
// TODO could also be good to add checkbox that lets users configure a workspace to never be auto-cleaned.
// TODO check instead for SCMTriggerItem:
@@ -143,11 +136,69 @@ private boolean shouldBeDeleted(@NonNull TopLevelItem item, FilePath dir, @NonNu
return false;
}
}
-
- LOGGER.log(Level.FINER, "Going to delete directory {0}", dir);
return true;
}
+ private static class CleanupOldWorkspaces extends MasterToSlaveFileCallable {
+
+ private final int retentionInDays;
+
+ CleanupOldWorkspaces(int retentionInDays) {
+ this.retentionInDays = retentionInDays;
+ }
+
+ @Override
+ public Void invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
+ File[] workspaces = null;
+ File parentWs = f.getParentFile();
+ if (parentWs != null) {
+ workspaces = parentWs.listFiles(new ShouldBeDeletedFilter(this.retentionInDays, f.getName()));
+ }
+
+ if (workspaces != null) {
+ for (File workspace : workspaces) {
+ LOGGER.log(Level.FINER, "Going to delete directory {0}", workspace);
+ Util.deleteRecursive(fileToPath(workspace), Path::toFile);
+ }
+ }
+ return null;
+ }
+ }
+
+ private static class ShouldBeDeletedFilter implements FileFilter, Serializable {
+
+ private final int retentionInDays;
+
+ private final String workspaceBaseName;
+
+ ShouldBeDeletedFilter(int retentionInDays, String workspaceBaseName) {
+ this.retentionInDays = retentionInDays;
+ this.workspaceBaseName = workspaceBaseName;
+ }
+
+ @Override
+ public boolean accept(File dir) {
+
+ if (!dir.isDirectory()) {
+ return false;
+ }
+
+ // if not the workspace or a workspace suffix
+ if (!dir.getName().equals(workspaceBaseName) && !dir.getName().startsWith(workspaceBaseName + WorkspaceList.COMBINATOR)) {
+ return false;
+ }
+
+ // if younger than a month, keep it
+ long now = new Date().getTime();
+ if (dir.lastModified() + this.retentionInDays * DAY > now) {
+ LOGGER.log(Level.FINE, "Directory {0} is only {1} old, so not deleting", new Object[] {dir, Util.getTimeSpanString(now - dir.lastModified())});
+ return false;
+ }
+
+ return true;
+ }
+ }
+
private static final Logger LOGGER = Logger.getLogger(WorkspaceCleanupThread.class.getName());
/**
diff --git a/core/src/main/java/hudson/model/listeners/ItemListener.java b/core/src/main/java/hudson/model/listeners/ItemListener.java
index aef431c3207b..3f20e2704b68 100644
--- a/core/src/main/java/hudson/model/listeners/ItemListener.java
+++ b/core/src/main/java/hudson/model/listeners/ItemListener.java
@@ -35,6 +35,8 @@
import java.util.logging.Level;
import java.util.logging.Logger;
import jenkins.util.Listeners;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
/**
* Receives notifications about CRUD operations of {@link Item}.
@@ -94,6 +96,16 @@ public void onCopied(Item src, Item item) {
public void onLoaded() {
}
+ /**
+ * Called before an item is deleted, providing the ability to veto the deletion operation before it starts.
+ * @param item the item being deleted
+ * @throws Failure to veto the operation.
+ * @throws InterruptedException If a blocking condition was interrupted, also vetoing the operation.
+ * @since 2.470
+ */
+ public void onCheckDelete(Item item) throws Failure, InterruptedException {
+ }
+
/**
* Called right before a job is going to be deleted.
*
@@ -205,6 +217,19 @@ public static void fireOnUpdated(final Item item) {
Listeners.notify(ItemListener.class, false, l -> l.onUpdated(item));
}
+ @Restricted(NoExternalUse.class)
+ public static void checkBeforeDelete(Item item) throws Failure, InterruptedException {
+ for (ItemListener l : all()) {
+ try {
+ l.onCheckDelete(item);
+ } catch (Failure e) {
+ throw e;
+ } catch (RuntimeException x) {
+ LOGGER.log(Level.WARNING, "failed to send event to listener of " + l.getClass(), x);
+ }
+ }
+ }
+
/** @since 1.548 */
public static void fireOnDeleted(final Item item) {
Listeners.notify(ItemListener.class, false, l -> l.onDeleted(item));
diff --git a/core/src/main/java/hudson/model/queue/MappingWorksheet.java b/core/src/main/java/hudson/model/queue/MappingWorksheet.java
index 54829ec0dac2..8150dd1bc132 100644
--- a/core/src/main/java/hudson/model/queue/MappingWorksheet.java
+++ b/core/src/main/java/hudson/model/queue/MappingWorksheet.java
@@ -47,6 +47,7 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.logging.Logger;
/**
* Defines a mapping problem for answering "where do we execute this task?"
@@ -85,6 +86,9 @@
* @author Kohsuke Kawaguchi
*/
public class MappingWorksheet {
+
+ private static final Logger LOGGER = Logger.getLogger(MappingWorksheet.class.getName());
+
public final List executors;
public final List works;
/**
@@ -135,8 +139,10 @@ public boolean canAccept(WorkChunk c) {
if (c.assignedLabel != null && !c.assignedLabel.contains(node))
return false; // label mismatch
- if (!(Node.SKIP_BUILD_CHECK_ON_FLYWEIGHTS && item.task instanceof Queue.FlyweightTask) && !nodeAcl.hasPermission2(item.authenticate2(), Computer.BUILD))
- return false; // tasks don't have a permission to run on this node
+ if (!(Node.SKIP_BUILD_CHECK_ON_FLYWEIGHTS && item.task instanceof Queue.FlyweightTask) && !nodeAcl.hasPermission2(item.authenticate2(), Computer.BUILD)) {
+ LOGGER.fine(() -> "Agent/Build permission denied to " + item.authenticate2().getName() + " on " + node.getNodeName());
+ return false;
+ }
return true;
}
@@ -184,7 +190,9 @@ public class WorkChunk extends ReadOnlyList {
* If the previous execution of this task run on a certain node
* and this task prefers to run on the same node, return that.
* Otherwise null.
+ * @deprecated Unused.
*/
+ @Deprecated
public final ExecutorChunk lastBuiltOn;
@@ -194,6 +202,7 @@ private WorkChunk(List base, int index) {
this.index = index;
this.assignedLabel = getAssignedLabel(base.get(0));
+ @SuppressWarnings("deprecation")
Node lbo = base.get(0).getLastBuiltOn();
for (ExecutorChunk ec : executors) {
if (ec.node == lbo) {
diff --git a/core/src/main/java/hudson/model/queue/QueueTaskFilter.java b/core/src/main/java/hudson/model/queue/QueueTaskFilter.java
index e2a113dfaa59..a36c5ca7c753 100644
--- a/core/src/main/java/hudson/model/queue/QueueTaskFilter.java
+++ b/core/src/main/java/hudson/model/queue/QueueTaskFilter.java
@@ -52,6 +52,7 @@ public Label getAssignedLabel() {
return base.getAssignedLabel();
}
+ @Deprecated
@Override
public Node getLastBuiltOn() {
return base.getLastBuiltOn();
diff --git a/core/src/main/java/hudson/model/queue/SubTask.java b/core/src/main/java/hudson/model/queue/SubTask.java
index f8b7dd435088..0690d074617c 100644
--- a/core/src/main/java/hudson/model/queue/SubTask.java
+++ b/core/src/main/java/hudson/model/queue/SubTask.java
@@ -62,7 +62,9 @@ default Label getAssignedLabel() {
* and this task prefers to run on the same node, return that.
* Otherwise null.
* @return by default, null
+ * @deprecated Unused.
*/
+ @Deprecated
default Node getLastBuiltOn() {
return null;
}
diff --git a/core/src/main/java/hudson/model/userproperty/UserPropertyCategory.java b/core/src/main/java/hudson/model/userproperty/UserPropertyCategory.java
new file mode 100644
index 000000000000..5d5467b6eed4
--- /dev/null
+++ b/core/src/main/java/hudson/model/userproperty/UserPropertyCategory.java
@@ -0,0 +1,204 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2022, CloudBees, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package hudson.model.userproperty;
+
+import edu.umd.cs.findbugs.annotations.NonNull;
+import hudson.Extension;
+import hudson.ExtensionList;
+import hudson.ExtensionPoint;
+import hudson.model.ModelObject;
+import hudson.model.UserProperty;
+import hudson.model.UserPropertyDescriptor;
+import org.jenkinsci.Symbol;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.DoNotUse;
+
+/**
+ * Grouping of related {@link UserProperty}s.
+ *
+ *
+ * To facilitate the separation of the user properties into multiple pages, tabs, and so on,
+ * {@link UserProperty}s are classified into categories (such as "security", "preferences", as well
+ * as the catch-all "unclassified".) Categories themselves are extensible — plugins may introduce
+ * its own category as well, although that should only happen if you are creating a big enough subsystem.
+ *
+ * @since 2.468
+ * @see UserProperty
+ */
+public abstract class UserPropertyCategory implements ExtensionPoint, ModelObject {
+ /**
+ * One-line plain text message that explains what this category is about.
+ * This can be used in the UI to help the user pick the right category.
+ *
+ * The text should be longer than {@link #getDisplayName()}
+ */
+ public abstract String getShortDescription();
+
+ /**
+ * Returns all the registered {@link UserPropertyCategory} descriptors.
+ */
+ public static ExtensionList all() {
+ return ExtensionList.lookup(UserPropertyCategory.class);
+ }
+
+ public static @NonNull T get(Class type) {
+ T category = all().get(type);
+ if (category == null) {
+ throw new AssertionError("Category not found. It seems the " + type + " is not annotated with @Extension and so not registered");
+ }
+ return category;
+ }
+
+ /**
+ * This category is used when the {@link hudson.model.UserPropertyDescriptor} has not implemented
+ * the {@link UserPropertyDescriptor#getUserPropertyCategory()} method
+ * (or the getUserPropertyCategoryAsString method for compatibility reason).
+ *
+ * If you do not know what to use, choose the {@link Account} instead of this one.
+ */
+ @Extension
+ @Symbol("unclassified")
+ @Restricted(DoNotUse.class)
+ public static class Unclassified extends UserPropertyCategory {
+ @Override
+ public String getDisplayName() {
+ return Messages.UserPropertyCategory_Unclassified_DisplayName();
+ }
+
+ @Override
+ public String getShortDescription() {
+ return Messages.UserPropertyCategory_Unclassified_ShortDescription();
+ }
+ }
+
+ /**
+ * User property related to account settings (e.g. timezone, email, ...).
+ *
+ * It could be seen as the default choice for {@link UserProperty} that are defining their category.
+ * Currently it has the same effect as {@link Unclassified} but the behavior could change in the future.
+ */
+ @Extension
+ @Symbol("account")
+ public static class Account extends UserPropertyCategory {
+ @Override
+ public String getDisplayName() {
+ return Messages.UserPropertyCategory_Account_DisplayName();
+ }
+
+ @Override
+ public String getShortDescription() {
+ return Messages.UserPropertyCategory_Account_ShortDescription();
+ }
+ }
+
+ /**
+ * Preferences related configurations (e.g. notification type, default view, ...).
+ */
+ @Extension
+ @Symbol("preferences")
+ public static class Preferences extends UserPropertyCategory {
+ @Override
+ public String getDisplayName() {
+ return Messages.UserPropertyCategory_Preferences_DisplayName();
+ }
+
+ @Override
+ public String getShortDescription() {
+ return Messages.UserPropertyCategory_Preferences_ShortDescription();
+ }
+ }
+
+ /**
+ * Per user feature flags (e.g. new design, ...).
+ */
+ @Extension
+ @Symbol("experimental")
+ public static class Experimental extends UserPropertyCategory {
+ @Override
+ public String getDisplayName() {
+ return Messages.UserPropertyCategory_Experimental_DisplayName();
+ }
+
+ @Override
+ public String getShortDescription() {
+ return Messages.UserPropertyCategory_Experimental_ShortDescription();
+ }
+ }
+
+ /**
+ * User interface related configurations (e.g. theme, language, ...).
+ *
+ * See also {@link jenkins.appearance.AppearanceCategory}.
+ */
+ @Extension
+ @Symbol("appearance")
+ public static class Appearance extends UserPropertyCategory {
+ @Override
+ public String getDisplayName() {
+ return Messages.UserPropertyCategory_Appearance_DisplayName();
+ }
+
+ @Override
+ public String getShortDescription() {
+ return Messages.UserPropertyCategory_Appearance_ShortDescription();
+ }
+ }
+
+
+ /**
+ * Security related configurations (e.g. API Token, SSH keys, ...).
+ * With this separation, we can more easily add control on their modifications.
+ */
+ @Extension
+ @Symbol("security")
+ public static class Security extends UserPropertyCategory {
+ @Override
+ public String getDisplayName() {
+ return Messages.UserPropertyCategory_Security_DisplayName();
+ }
+
+ @Override
+ public String getShortDescription() {
+ return Messages.UserPropertyCategory_Security_ShortDescription();
+ }
+ }
+
+ /**
+ * For user properties that are not expected to be displayed,
+ * typically automatically configured by automated behavior, without direct user interaction.
+ */
+ @Extension
+ @Symbol("invisible")
+ public static class Invisible extends UserPropertyCategory {
+ @Override
+ public String getDisplayName() {
+ return Messages.UserPropertyCategory_Invisible_DisplayName();
+ }
+
+ @Override
+ public String getShortDescription() {
+ return Messages.UserPropertyCategory_Invisible_ShortDescription();
+ }
+ }
+}
diff --git a/core/src/main/java/hudson/model/userproperty/UserPropertyCategoryAccountAction.java b/core/src/main/java/hudson/model/userproperty/UserPropertyCategoryAccountAction.java
new file mode 100644
index 000000000000..822cdc6f4c99
--- /dev/null
+++ b/core/src/main/java/hudson/model/userproperty/UserPropertyCategoryAccountAction.java
@@ -0,0 +1,121 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2022, CloudBees, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package hudson.model.userproperty;
+
+import edu.umd.cs.findbugs.annotations.NonNull;
+import hudson.DescriptorExtensionList;
+import hudson.Extension;
+import hudson.model.Action;
+import hudson.model.Descriptor;
+import hudson.model.TransientUserActionFactory;
+import hudson.model.User;
+import hudson.model.UserProperty;
+import hudson.model.UserPropertyDescriptor;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import javax.servlet.ServletException;
+import jenkins.model.Jenkins;
+import jenkins.security.UserDetailsCache;
+import net.sf.json.JSONObject;
+import org.jenkinsci.Symbol;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
+import org.kohsuke.stapler.StaplerRequest;
+import org.kohsuke.stapler.StaplerResponse;
+import org.kohsuke.stapler.verb.POST;
+
+@Restricted(NoExternalUse.class)
+public class UserPropertyCategoryAccountAction extends UserPropertyCategoryAction implements Action {
+ public UserPropertyCategoryAccountAction(@NonNull User user) {
+ super(user);
+ }
+
+ @Override
+ public String getDisplayName() {
+ return Messages.UserPropertyCategoryAccountAction_DisplayName();
+ }
+
+ @Override
+ public String getIconFileName() {
+ return getTargetUser().hasPermission(Jenkins.ADMINISTER) ? "symbol-settings" : null;
+ }
+
+ @Override
+ public String getUrlName() {
+ return "account";
+ }
+
+ public @NonNull List getMyCategoryDescriptors() {
+ return allByTwoCategoryClasses(UserPropertyCategory.Unclassified.class, UserPropertyCategory.Account.class);
+ }
+
+ private static List allByTwoCategoryClasses(
+ @NonNull Class extends UserPropertyCategory> categoryClass1,
+ @NonNull Class extends UserPropertyCategory> categoryClass2
+ ) {
+ DescriptorExtensionList all = UserProperty.all();
+
+ List filteredList = new ArrayList<>(all.size());
+ for (UserPropertyDescriptor descriptor : all) {
+ Class extends UserPropertyCategory> currClass = descriptor.getUserPropertyCategory().getClass();
+ if (currClass.equals(categoryClass1) || currClass.equals(categoryClass2)) {
+ filteredList.add(descriptor);
+ }
+ }
+
+ return filteredList;
+ }
+
+ @POST
+ public void doConfigSubmit(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, Descriptor.FormException {
+ User targetUser = this.getTargetUser();
+ targetUser.checkPermission(Jenkins.ADMINISTER);
+
+ JSONObject json = req.getSubmittedForm();
+
+ String oldFullName = targetUser.getFullName();
+ targetUser.setFullName(json.getString("fullName"));
+ targetUser.setDescription(json.getString("description"));
+
+ super.doConfigSubmit(req, rsp);
+
+ if (!oldFullName.equals(targetUser.getFullName())) {
+ UserDetailsCache.get().invalidate(oldFullName);
+ }
+ }
+
+ /**
+ * Inject the outer class configuration page into the sidenav and the request routing of the user
+ */
+ @Extension(ordinal = 400)
+ @Symbol("account")
+ public static class AccountActionFactory extends TransientUserActionFactory {
+ public Collection extends Action> createFor(User target) {
+ return Collections.singleton(new UserPropertyCategoryAccountAction(target));
+ }
+ }
+}
diff --git a/core/src/main/java/hudson/model/userproperty/UserPropertyCategoryAction.java b/core/src/main/java/hudson/model/userproperty/UserPropertyCategoryAction.java
new file mode 100644
index 000000000000..caec7c1bdf88
--- /dev/null
+++ b/core/src/main/java/hudson/model/userproperty/UserPropertyCategoryAction.java
@@ -0,0 +1,65 @@
+package hudson.model.userproperty;
+
+import edu.umd.cs.findbugs.annotations.NonNull;
+import hudson.model.Descriptor;
+import hudson.model.User;
+import hudson.model.UserProperty;
+import hudson.model.UserPropertyDescriptor;
+import hudson.util.FormApply;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import javax.servlet.ServletException;
+import jenkins.model.Jenkins;
+import net.sf.json.JSONObject;
+import org.kohsuke.stapler.StaplerRequest;
+import org.kohsuke.stapler.StaplerResponse;
+import org.kohsuke.stapler.verb.POST;
+
+public abstract class UserPropertyCategoryAction {
+
+ private final User targetUser;
+
+ public UserPropertyCategoryAction(User targetUser) {
+ this.targetUser = targetUser;
+ }
+
+ public @NonNull User getTargetUser() {
+ return targetUser;
+ }
+
+ public @NonNull abstract List getMyCategoryDescriptors();
+
+ @POST
+ public void doConfigSubmit(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, Descriptor.FormException {
+ this.targetUser.checkPermission(Jenkins.ADMINISTER);
+
+ JSONObject json = req.getSubmittedForm();
+
+ List props = new ArrayList<>();
+ List myCategoryDescriptors = getMyCategoryDescriptors();
+ int i = 0;
+ for (UserPropertyDescriptor d : myCategoryDescriptors) {
+ UserProperty p = this.targetUser.getProperty(d.clazz);
+
+ JSONObject o = json.optJSONObject("userProperty" + i++);
+ if (o != null) {
+ if (p != null) {
+ p = p.reconfigure(req, o);
+ } else {
+ p = d.newInstance(req, o);
+ }
+ }
+
+ if (p != null) {
+ props.add(p);
+ }
+ }
+ this.targetUser.addProperties(props);
+
+ this.targetUser.save();
+
+ // we are in /user///, going to /user//
+ FormApply.success("..").generateResponse(req, rsp, this);
+ }
+}
diff --git a/core/src/main/java/hudson/model/userproperty/UserPropertyCategoryAppearanceAction.java b/core/src/main/java/hudson/model/userproperty/UserPropertyCategoryAppearanceAction.java
new file mode 100644
index 000000000000..88d08a8add70
--- /dev/null
+++ b/core/src/main/java/hudson/model/userproperty/UserPropertyCategoryAppearanceAction.java
@@ -0,0 +1,76 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2022, CloudBees, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package hudson.model.userproperty;
+
+import edu.umd.cs.findbugs.annotations.NonNull;
+import hudson.Extension;
+import hudson.model.Action;
+import hudson.model.TransientUserActionFactory;
+import hudson.model.User;
+import hudson.model.UserProperty;
+import hudson.model.UserPropertyDescriptor;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import jenkins.model.Jenkins;
+import org.jenkinsci.Symbol;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
+
+@Restricted(NoExternalUse.class)
+public class UserPropertyCategoryAppearanceAction extends UserPropertyCategoryAction implements Action {
+ public UserPropertyCategoryAppearanceAction(@NonNull User user) {
+ super(user);
+ }
+
+ @Override
+ public String getDisplayName() {
+ return Messages.UserPropertyCategoryAppearanceAction_DisplayName();
+ }
+
+ @Override
+ public String getIconFileName() {
+ return getTargetUser().hasPermission(Jenkins.ADMINISTER) ? "symbol-brush-outline" : null;
+ }
+
+ @Override
+ public String getUrlName() {
+ return "appearance";
+ }
+
+ public @NonNull List getMyCategoryDescriptors() {
+ return UserProperty.allByCategoryClass(UserPropertyCategory.Appearance.class);
+ }
+
+ /**
+ * Inject the outer class configuration page into the sidenav and the request routing of the user
+ */
+ @Extension(ordinal = 350)
+ @Symbol("appearance")
+ public static class AppearanceActionFactory extends TransientUserActionFactory {
+ public Collection extends Action> createFor(User target) {
+ return Collections.singleton(new UserPropertyCategoryAppearanceAction(target));
+ }
+ }
+}
diff --git a/core/src/main/java/hudson/model/userproperty/UserPropertyCategoryExperimentalAction.java b/core/src/main/java/hudson/model/userproperty/UserPropertyCategoryExperimentalAction.java
new file mode 100644
index 000000000000..bb242bacad7a
--- /dev/null
+++ b/core/src/main/java/hudson/model/userproperty/UserPropertyCategoryExperimentalAction.java
@@ -0,0 +1,76 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2022, CloudBees, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package hudson.model.userproperty;
+
+import edu.umd.cs.findbugs.annotations.NonNull;
+import hudson.Extension;
+import hudson.model.Action;
+import hudson.model.TransientUserActionFactory;
+import hudson.model.User;
+import hudson.model.UserProperty;
+import hudson.model.UserPropertyDescriptor;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import jenkins.model.Jenkins;
+import org.jenkinsci.Symbol;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
+
+@Restricted(NoExternalUse.class)
+public class UserPropertyCategoryExperimentalAction extends UserPropertyCategoryAction implements Action {
+ public UserPropertyCategoryExperimentalAction(@NonNull User user) {
+ super(user);
+ }
+
+ @Override
+ public String getDisplayName() {
+ return Messages.UserPropertyCategoryExperimentalAction_DisplayName();
+ }
+
+ @Override
+ public String getIconFileName() {
+ return getTargetUser().hasPermission(Jenkins.ADMINISTER) ? "symbol-flask" : null;
+ }
+
+ @Override
+ public String getUrlName() {
+ return "experiments";
+ }
+
+ public @NonNull List getMyCategoryDescriptors() {
+ return UserProperty.allByCategoryClass(UserPropertyCategory.Experimental.class);
+ }
+
+ /**
+ * Inject the outer class configuration page into the sidenav and the request routing of the user
+ */
+ @Extension(ordinal = 100)
+ @Symbol("experimental")
+ public static class ExperimentalActionFactory extends TransientUserActionFactory {
+ public Collection extends Action> createFor(User target) {
+ return Collections.singleton(new UserPropertyCategoryExperimentalAction(target));
+ }
+ }
+}
diff --git a/core/src/main/java/hudson/model/userproperty/UserPropertyCategoryPreferencesAction.java b/core/src/main/java/hudson/model/userproperty/UserPropertyCategoryPreferencesAction.java
new file mode 100644
index 000000000000..7a74b702e6c0
--- /dev/null
+++ b/core/src/main/java/hudson/model/userproperty/UserPropertyCategoryPreferencesAction.java
@@ -0,0 +1,76 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2022, CloudBees, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package hudson.model.userproperty;
+
+import edu.umd.cs.findbugs.annotations.NonNull;
+import hudson.Extension;
+import hudson.model.Action;
+import hudson.model.TransientUserActionFactory;
+import hudson.model.User;
+import hudson.model.UserProperty;
+import hudson.model.UserPropertyDescriptor;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import jenkins.model.Jenkins;
+import org.jenkinsci.Symbol;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
+
+@Restricted(NoExternalUse.class)
+public class UserPropertyCategoryPreferencesAction extends UserPropertyCategoryAction implements Action {
+ public UserPropertyCategoryPreferencesAction(@NonNull User user) {
+ super(user);
+ }
+
+ @Override
+ public String getDisplayName() {
+ return Messages.UserPropertyCategoryPreferencesAction_DisplayName();
+ }
+
+ @Override
+ public String getIconFileName() {
+ return getTargetUser().hasPermission(Jenkins.ADMINISTER) ? "symbol-parameters" : null;
+ }
+
+ @Override
+ public String getUrlName() {
+ return "preferences";
+ }
+
+ public @NonNull List getMyCategoryDescriptors() {
+ return UserProperty.allByCategoryClass(UserPropertyCategory.Preferences.class);
+ }
+
+ /**
+ * Inject the outer class configuration page into the sidenav and the request routing of the user
+ */
+ @Extension(ordinal = 300)
+ @Symbol("preferences")
+ public static class PreferencesActionFactory extends TransientUserActionFactory {
+ public Collection extends Action> createFor(User target) {
+ return Collections.singleton(new UserPropertyCategoryPreferencesAction(target));
+ }
+ }
+}
diff --git a/core/src/main/java/hudson/model/userproperty/UserPropertyCategorySecurityAction.java b/core/src/main/java/hudson/model/userproperty/UserPropertyCategorySecurityAction.java
new file mode 100644
index 000000000000..a6cb3e6ed3c4
--- /dev/null
+++ b/core/src/main/java/hudson/model/userproperty/UserPropertyCategorySecurityAction.java
@@ -0,0 +1,77 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2022, CloudBees, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package hudson.model.userproperty;
+
+
+import edu.umd.cs.findbugs.annotations.NonNull;
+import hudson.Extension;
+import hudson.model.Action;
+import hudson.model.TransientUserActionFactory;
+import hudson.model.User;
+import hudson.model.UserProperty;
+import hudson.model.UserPropertyDescriptor;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import jenkins.model.Jenkins;
+import org.jenkinsci.Symbol;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
+
+@Restricted(NoExternalUse.class)
+public class UserPropertyCategorySecurityAction extends UserPropertyCategoryAction implements Action {
+ public UserPropertyCategorySecurityAction(@NonNull User user) {
+ super(user);
+ }
+
+ @Override
+ public String getDisplayName() {
+ return Messages.UserPropertyCategorySecurityAction_DisplayName();
+ }
+
+ @Override
+ public String getIconFileName() {
+ return getTargetUser().hasPermission(Jenkins.ADMINISTER) ? "symbol-lock-closed" : null;
+ }
+
+ @Override
+ public String getUrlName() {
+ return "security";
+ }
+
+ public @NonNull List getMyCategoryDescriptors() {
+ return UserProperty.allByCategoryClass(UserPropertyCategory.Security.class);
+ }
+
+ /**
+ * Inject the outer class configuration page into the sidenav and the request routing of the user
+ */
+ @Extension(ordinal = 200)
+ @Symbol("security")
+ public static class SecurityActionFactory extends TransientUserActionFactory {
+ public Collection extends Action> createFor(User target) {
+ return Collections.singleton(new UserPropertyCategorySecurityAction(target));
+ }
+ }
+}
diff --git a/core/src/main/java/hudson/scm/ChangeLogSet.java b/core/src/main/java/hudson/scm/ChangeLogSet.java
index a0c4a4c9cd79..f9f29720355f 100644
--- a/core/src/main/java/hudson/scm/ChangeLogSet.java
+++ b/core/src/main/java/hudson/scm/ChangeLogSet.java
@@ -238,7 +238,7 @@ public Collection extends AffectedFile> getAffectedFiles() {
ChangeLogSet parent = getParent();
if (null != parent) {
String kind = parent.getKind();
- if (null != kind && kind.trim().length() > 0) scm = kind;
+ if (null != kind && !kind.trim().isEmpty()) scm = kind;
}
throw new UnsupportedOperationException("getAffectedFiles() is not implemented by " + scm);
}
diff --git a/core/src/main/java/hudson/scm/browsers/QueryBuilder.java b/core/src/main/java/hudson/scm/browsers/QueryBuilder.java
index 06d933aa7ef3..72e6ebc0d557 100644
--- a/core/src/main/java/hudson/scm/browsers/QueryBuilder.java
+++ b/core/src/main/java/hudson/scm/browsers/QueryBuilder.java
@@ -38,7 +38,7 @@ public QueryBuilder(String s) {
public QueryBuilder add(String s) {
if (s == null) return this; // nothing to add
- if (buf.length() == 0) buf.append('?');
+ if (buf.isEmpty()) buf.append('?');
else buf.append('&');
buf.append(s);
return this;
diff --git a/core/src/main/java/hudson/search/FixedSet.java b/core/src/main/java/hudson/search/FixedSet.java
index 3c3819f03bcb..f77e20623542 100644
--- a/core/src/main/java/hudson/search/FixedSet.java
+++ b/core/src/main/java/hudson/search/FixedSet.java
@@ -27,6 +27,7 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
+import java.util.Locale;
/**
* Set of {@link SearchItem}s that are statically known upfront.
@@ -61,7 +62,7 @@ public void suggest(String token, List result) {
boolean caseInsensitive = UserSearchProperty.isCaseInsensitive();
for (SearchItem i : items) {
String name = i.getSearchName();
- if (name != null && (name.contains(token) || (caseInsensitive && name.toLowerCase().contains(token.toLowerCase())))) {
+ if (name != null && (name.contains(token) || (caseInsensitive && name.toLowerCase(Locale.ROOT).contains(token.toLowerCase(Locale.ROOT))))) {
result.add(i);
}
}
diff --git a/core/src/main/java/hudson/search/ParsedQuickSilver.java b/core/src/main/java/hudson/search/ParsedQuickSilver.java
index dc214892ac00..879f9c080d16 100644
--- a/core/src/main/java/hudson/search/ParsedQuickSilver.java
+++ b/core/src/main/java/hudson/search/ParsedQuickSilver.java
@@ -85,7 +85,7 @@ private ParsedQuickSilver(Class extends SearchableModelObject> clazz) {
private String splitName(String url) {
StringBuilder buf = new StringBuilder(url.length() + 5);
for (String token : url.split("(?<=[a-z])(?=[A-Z])")) {
- if (buf.length() > 0) buf.append(' ');
+ if (!buf.isEmpty()) buf.append(' ');
buf.append(Introspector.decapitalize(token));
}
return buf.toString();
diff --git a/core/src/main/java/hudson/search/SuggestedItem.java b/core/src/main/java/hudson/search/SuggestedItem.java
index 86dbc36b5fe3..9ba455270e58 100644
--- a/core/src/main/java/hudson/search/SuggestedItem.java
+++ b/core/src/main/java/hudson/search/SuggestedItem.java
@@ -111,7 +111,7 @@ private void getUrl(StringBuilder buf) {
buf.setLength(0);
buf.append(f);
} else {
- if (buf.length() == 0 || buf.charAt(buf.length() - 1) != '/')
+ if (buf.isEmpty() || buf.charAt(buf.length() - 1) != '/')
buf.append('/');
buf.append(f);
}
diff --git a/core/src/main/java/hudson/search/UserSearchProperty.java b/core/src/main/java/hudson/search/UserSearchProperty.java
index 7e9836944c0b..a3515dd08874 100644
--- a/core/src/main/java/hudson/search/UserSearchProperty.java
+++ b/core/src/main/java/hudson/search/UserSearchProperty.java
@@ -5,6 +5,7 @@
import hudson.model.User;
import hudson.model.UserProperty;
import hudson.model.UserPropertyDescriptor;
+import hudson.model.userproperty.UserPropertyCategory;
import net.sf.json.JSONObject;
import org.jenkinsci.Symbol;
import org.kohsuke.stapler.StaplerRequest;
@@ -54,6 +55,10 @@ public UserProperty newInstance(StaplerRequest req, JSONObject formData) throws
return new UserSearchProperty(formData.optBoolean("insensitiveSearch"));
}
+ @Override
+ public @NonNull UserPropertyCategory getUserPropertyCategory() {
+ return UserPropertyCategory.get(UserPropertyCategory.Preferences.class);
+ }
}
}
diff --git a/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java b/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java
index 6305a69a6abb..bd122244c7e2 100644
--- a/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java
+++ b/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java
@@ -39,6 +39,7 @@
import hudson.model.User;
import hudson.model.UserProperty;
import hudson.model.UserPropertyDescriptor;
+import hudson.model.userproperty.UserPropertyCategory;
import hudson.security.FederatedLoginService.FederatedIdentity;
import hudson.security.captcha.CaptchaSupport;
import hudson.util.FormValidation;
@@ -449,7 +450,7 @@ private SignupInfo validateAccountCreationForm(StaplerRequest req, boolean valid
si.errors.put("password1", Messages.HudsonPrivateSecurityRealm_CreateAccount_PasswordNotMatch());
}
- if (!(si.password1 != null && si.password1.length() != 0)) {
+ if (!(si.password1 != null && !si.password1.isEmpty())) {
si.errors.put("password1", Messages.HudsonPrivateSecurityRealm_CreateAccount_PasswordRequired());
}
@@ -801,7 +802,6 @@ public boolean equals(Object o) {
public int hashCode() {
return getUsername().hashCode();
}
-
}
public static class ConverterImpl extends XStream2.PassthruConverter {
@@ -884,6 +884,11 @@ public boolean isEnabled() {
public UserProperty newInstance(User user) {
return null;
}
+
+ @Override
+ public @NonNull UserPropertyCategory getUserPropertyCategory() {
+ return UserPropertyCategory.get(UserPropertyCategory.Security.class);
+ }
}
}
diff --git a/core/src/main/java/hudson/security/SecurityRealm.java b/core/src/main/java/hudson/security/SecurityRealm.java
index 525368f65692..b969a57eb781 100644
--- a/core/src/main/java/hudson/security/SecurityRealm.java
+++ b/core/src/main/java/hudson/security/SecurityRealm.java
@@ -326,7 +326,7 @@ public void doLogout(StaplerRequest req, StaplerResponse rsp) throws IOException
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
SecurityContextHolder.clearContext();
- String contextPath = req.getContextPath().length() > 0 ? req.getContextPath() : "/";
+ String contextPath = !req.getContextPath().isEmpty() ? req.getContextPath() : "/";
resetRememberMeCookie(req, rsp, contextPath);
clearStaleSessionCookies(req, rsp, contextPath);
diff --git a/core/src/main/java/hudson/security/csrf/CrumbFilter.java b/core/src/main/java/hudson/security/csrf/CrumbFilter.java
index 03e17a0fd894..8b4b558938e7 100644
--- a/core/src/main/java/hudson/security/csrf/CrumbFilter.java
+++ b/core/src/main/java/hudson/security/csrf/CrumbFilter.java
@@ -105,7 +105,7 @@ private static String canonicalPath(String path) {
buf.append(token);
}
// translation: if (path.endsWith("/") && !buf.endsWith("/"))
- if (path.endsWith("/") && (buf.length() == 0 || buf.charAt(buf.length() - 1) != '/'))
+ if (path.endsWith("/") && (buf.isEmpty() || buf.charAt(buf.length() - 1) != '/'))
buf.append('/');
return buf.toString();
}
diff --git a/core/src/main/java/hudson/security/csrf/CrumbIssuer.java b/core/src/main/java/hudson/security/csrf/CrumbIssuer.java
index 384f0a765ddd..cd0c51b03fb0 100644
--- a/core/src/main/java/hudson/security/csrf/CrumbIssuer.java
+++ b/core/src/main/java/hudson/security/csrf/CrumbIssuer.java
@@ -78,7 +78,7 @@ public String getCrumb(ServletRequest request) {
if (crumb == null) {
crumb = issueCrumb(request, getDescriptor().getCrumbSalt());
if (request != null) {
- if (crumb != null && crumb.length() > 0) {
+ if (crumb != null && !crumb.isEmpty()) {
request.setAttribute(CRUMB_ATTRIBUTE, crumb);
} else {
request.removeAttribute(CRUMB_ATTRIBUTE);
@@ -204,7 +204,7 @@ public static class RestrictedApi extends Api {
text = null;
}
if (text != null) {
- try (OutputStream o = rsp.getCompressedOutputStream(req)) {
+ try (OutputStream o = rsp.getOutputStream()) {
rsp.setContentType("text/plain;charset=UTF-8");
o.write(text.getBytes(StandardCharsets.UTF_8));
}
diff --git a/core/src/main/java/hudson/slaves/CloudRetentionStrategy.java b/core/src/main/java/hudson/slaves/CloudRetentionStrategy.java
index ec0142dd4768..e4601a2fad31 100644
--- a/core/src/main/java/hudson/slaves/CloudRetentionStrategy.java
+++ b/core/src/main/java/hudson/slaves/CloudRetentionStrategy.java
@@ -64,7 +64,7 @@ public long check(final AbstractCloudComputer c) {
}
}
}
- return 1;
+ return 0;
}
/**
diff --git a/core/src/main/java/hudson/slaves/CloudSlaveRetentionStrategy.java b/core/src/main/java/hudson/slaves/CloudSlaveRetentionStrategy.java
index 4e1b376caffe..a8dfeba1b20a 100644
--- a/core/src/main/java/hudson/slaves/CloudSlaveRetentionStrategy.java
+++ b/core/src/main/java/hudson/slaves/CloudSlaveRetentionStrategy.java
@@ -37,7 +37,7 @@ public long check(T c) {
}
}
}
- return checkCycle();
+ return TimeUnit.MILLISECONDS.toMinutes(checkCycle());
}
/**
diff --git a/core/src/main/java/hudson/slaves/ComputerRetentionWork.java b/core/src/main/java/hudson/slaves/ComputerRetentionWork.java
index ddee61115a03..42256855117d 100644
--- a/core/src/main/java/hudson/slaves/ComputerRetentionWork.java
+++ b/core/src/main/java/hudson/slaves/ComputerRetentionWork.java
@@ -25,13 +25,15 @@
package hudson.slaves;
import hudson.Extension;
+import hudson.ExtensionList;
+import hudson.model.AperiodicWork;
import hudson.model.Computer;
import hudson.model.Node;
-import hudson.model.PeriodicWork;
import hudson.model.Queue;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.TimeUnit;
+import jenkins.model.GlobalComputerRetentionCheckIntervalConfiguration;
import jenkins.model.Jenkins;
import org.jenkinsci.Symbol;
@@ -42,7 +44,7 @@
* @author Stephen Connolly
*/
@Extension @Symbol("computerRetention")
-public class ComputerRetentionWork extends PeriodicWork {
+public class ComputerRetentionWork extends AperiodicWork {
/**
* Use weak hash map to avoid leaking {@link Computer}.
@@ -51,12 +53,18 @@ public class ComputerRetentionWork extends PeriodicWork {
@Override
public long getRecurrencePeriod() {
- return MIN;
+ return ExtensionList.lookupSingleton(GlobalComputerRetentionCheckIntervalConfiguration.class).getComputerRetentionCheckInterval() * 1000L;
+ }
+
+ @Override
+ public AperiodicWork getNewInstance() {
+ // ComputerRetentionWork is a singleton.
+ return this;
}
@SuppressWarnings("unchecked")
@Override
- protected void doRun() {
+ protected void doAperiodicRun() {
final long startRun = System.currentTimeMillis();
for (final Computer c : Jenkins.get().getComputers()) {
Queue.withLock(new Runnable() {
@@ -67,8 +75,7 @@ public void run() {
return;
if (!nextCheck.containsKey(c) || startRun > nextCheck.get(c)) {
// at the moment I don't trust strategies to wait more than 60 minutes
- // strategies need to wait at least one minute
- final long waitInMins = Math.max(1, Math.min(60, c.getRetentionStrategy().check(c)));
+ final long waitInMins = Math.max(0, Math.min(60, c.getRetentionStrategy().check(c)));
nextCheck.put(c, startRun + TimeUnit.MINUTES.toMillis(waitInMins));
}
}
diff --git a/core/src/main/java/hudson/slaves/JNLPLauncher.java b/core/src/main/java/hudson/slaves/JNLPLauncher.java
index 9add9faf0c3f..4f0cbc1e6153 100644
--- a/core/src/main/java/hudson/slaves/JNLPLauncher.java
+++ b/core/src/main/java/hudson/slaves/JNLPLauncher.java
@@ -175,7 +175,7 @@ public String getTunnel() {
*/
@DataBoundSetter
public void setTunnel(String tunnel) {
- this.tunnel = tunnel;
+ this.tunnel = Util.fixEmptyAndTrim(tunnel);
}
@Override
diff --git a/core/src/main/java/hudson/slaves/RetentionStrategy.java b/core/src/main/java/hudson/slaves/RetentionStrategy.java
index e06b8e42c069..687e44d5c172 100644
--- a/core/src/main/java/hudson/slaves/RetentionStrategy.java
+++ b/core/src/main/java/hudson/slaves/RetentionStrategy.java
@@ -171,7 +171,7 @@ public Always() {
public long check(SlaveComputer c) {
if (c.isOffline() && !c.isConnecting() && c.isLaunchSupported())
c.tryReconnect();
- return 1;
+ return 0;
}
@Extension(ordinal = 100) @Symbol("always")
@@ -285,7 +285,7 @@ public long check(final SlaveComputer c) {
return TimeUnit.MILLISECONDS.toMinutes(TimeUnit.MINUTES.toMillis(idleDelay) - idleMilliseconds);
}
}
- return 1;
+ return 0;
}
@Extension @Symbol("demand")
diff --git a/core/src/main/java/hudson/slaves/SimpleScheduledRetentionStrategy.java b/core/src/main/java/hudson/slaves/SimpleScheduledRetentionStrategy.java
index ef6e3d0c8658..ea90529b29b4 100644
--- a/core/src/main/java/hudson/slaves/SimpleScheduledRetentionStrategy.java
+++ b/core/src/main/java/hudson/slaves/SimpleScheduledRetentionStrategy.java
@@ -209,7 +209,7 @@ public void run() {
LOGGER.log(INFO,
"Disabling new jobs for computer {0} as it has finished its scheduled uptime",
new Object[]{c.getName()});
- return 1;
+ return 0;
} else if (c.isIdle() && c.isAcceptingTasks()) {
Queue.withLock(new Runnable() {
@Override
@@ -243,7 +243,7 @@ public void run() {
}
}
}
- return 1;
+ return 0;
}
private synchronized boolean isOnlineScheduled() {
diff --git a/core/src/main/java/hudson/tasks/ArtifactArchiver.java b/core/src/main/java/hudson/tasks/ArtifactArchiver.java
index 02137ad36946..4300cd1538ab 100644
--- a/core/src/main/java/hudson/tasks/ArtifactArchiver.java
+++ b/core/src/main/java/hudson/tasks/ArtifactArchiver.java
@@ -389,7 +389,9 @@ public boolean isApplicable(Class extends AbstractProject> jobType) {
if (bd instanceof LogRotator) {
LogRotator lr = (LogRotator) bd;
if (lr.getArtifactNumToKeep() == -1) {
- p.setBuildDiscarder(new LogRotator(lr.getDaysToKeep(), lr.getNumToKeep(), lr.getArtifactDaysToKeep(), 1));
+ LogRotator newLr = new LogRotator(lr.getDaysToKeep(), lr.getNumToKeep(), lr.getArtifactDaysToKeep(), 1);
+ newLr.setRemoveLastBuild(lr.isRemoveLastBuild());
+ p.setBuildDiscarder(newLr);
} else {
LOG.log(Level.WARNING, "will not clobber artifactNumToKeep={0} in {1}", new Object[] {lr.getArtifactNumToKeep(), p});
}
diff --git a/core/src/main/java/hudson/tasks/Fingerprinter.java b/core/src/main/java/hudson/tasks/Fingerprinter.java
index 3be7631bb1f0..4d843b27b003 100644
--- a/core/src/main/java/hudson/tasks/Fingerprinter.java
+++ b/core/src/main/java/hudson/tasks/Fingerprinter.java
@@ -182,7 +182,7 @@ public void perform(Run, ?> build, FilePath workspace, EnvVars environment, La
Map record = new HashMap<>();
- if (targets.length() != 0) {
+ if (!targets.isEmpty()) {
String expandedTargets = targets;
if (build instanceof AbstractBuild) { // no expansion for pipelines
expandedTargets = environment.expand(expandedTargets);
diff --git a/core/src/main/java/hudson/tasks/LogRotator.java b/core/src/main/java/hudson/tasks/LogRotator.java
index 3d5f3975808a..c22d5af2e3d2 100644
--- a/core/src/main/java/hudson/tasks/LogRotator.java
+++ b/core/src/main/java/hudson/tasks/LogRotator.java
@@ -50,6 +50,7 @@
import jenkins.util.io.CompositeIOException;
import org.jenkinsci.Symbol;
import org.kohsuke.stapler.DataBoundConstructor;
+import org.kohsuke.stapler.DataBoundSetter;
/**
* Default implementation of {@link BuildDiscarder}.
@@ -108,6 +109,12 @@ public CollatedLogRotatorException(String msg, Collection values) {
*/
private final Integer artifactNumToKeep;
+ /**
+ * If enabled also remove last successful build.
+ * @since TODO
+ */
+ private boolean removeLastBuild;
+
@DataBoundConstructor
public LogRotator(String daysToKeepStr, String numToKeepStr, String artifactDaysToKeepStr, String artifactNumToKeepStr) {
this (parse(daysToKeepStr), parse(numToKeepStr),
@@ -140,6 +147,11 @@ public LogRotator(int daysToKeep, int numToKeep, int artifactDaysToKeep, int art
}
+ @DataBoundSetter
+ public void setRemoveLastBuild(boolean removeLastBuild) {
+ this.removeLastBuild = removeLastBuild;
+ }
+
@Override
@SuppressWarnings("rawtypes")
public void perform(Job, ?> job) throws IOException, InterruptedException {
@@ -148,9 +160,9 @@ public void perform(Job, ?> job) throws IOException, InterruptedException {
LOGGER.log(FINE, "Running the log rotation for {0} with numToKeep={1} daysToKeep={2} artifactNumToKeep={3} artifactDaysToKeep={4}", new Object[] {job, numToKeep, daysToKeep, artifactNumToKeep, artifactDaysToKeep});
- // always keep the last successful and the last stable builds
- Run lsb = job.getLastSuccessfulBuild();
- Run lstb = job.getLastStableBuild();
+ // if configured keep the last successful and the last stable builds
+ Run lsb = removeLastBuild ? null : job.getLastSuccessfulBuild();
+ Run lstb = removeLastBuild ? null : job.getLastStableBuild();
if (numToKeep != -1) {
// Note that RunList.size is deprecated, and indeed here we are loading all the builds of the job.
@@ -270,6 +282,10 @@ public int getArtifactNumToKeep() {
return unbox(artifactNumToKeep);
}
+ public boolean isRemoveLastBuild() {
+ return removeLastBuild;
+ }
+
public String getDaysToKeepStr() {
return toString(daysToKeep);
}
diff --git a/core/src/main/java/hudson/tools/AbstractCommandInstaller.java b/core/src/main/java/hudson/tools/AbstractCommandInstaller.java
index 46e7bc6657a2..88cbe5915cc0 100644
--- a/core/src/main/java/hudson/tools/AbstractCommandInstaller.java
+++ b/core/src/main/java/hudson/tools/AbstractCommandInstaller.java
@@ -90,7 +90,7 @@ public abstract static class Descriptor {
public FormValidation doCheckCommand(@QueryParameter String value) {
- if (value.length() > 0) {
+ if (!value.isEmpty()) {
return FormValidation.ok();
} else {
return FormValidation.error(Messages.CommandInstaller_no_command());
@@ -98,7 +98,7 @@ public FormValidation doCheckCommand(@QueryParameter String value) {
}
public FormValidation doCheckToolHome(@QueryParameter String value) {
- if (value.length() > 0) {
+ if (!value.isEmpty()) {
return FormValidation.ok();
} else {
return FormValidation.error(Messages.CommandInstaller_no_toolHome());
diff --git a/core/src/main/java/hudson/tools/InstallerTranslator.java b/core/src/main/java/hudson/tools/InstallerTranslator.java
index 35db845dd2e6..45e24cadf60d 100644
--- a/core/src/main/java/hudson/tools/InstallerTranslator.java
+++ b/core/src/main/java/hudson/tools/InstallerTranslator.java
@@ -59,11 +59,8 @@ public String getToolHome(Node node, ToolInstallation tool, TaskListener log) th
if (installer.appliesTo(node)) {
Semaphore semaphore;
synchronized (mutexByNode) {
- Map mutexByTool = mutexByNode.computeIfAbsent(node, k -> new WeakHashMap<>());
- semaphore = mutexByTool.get(tool);
- if (semaphore == null) {
- mutexByTool.put(tool, semaphore = new Semaphore(1));
- }
+ Map mutexByTool = mutexByNode.computeIfAbsent(node, unused -> new WeakHashMap<>());
+ semaphore = mutexByTool.computeIfAbsent(tool, unused -> new Semaphore(1));
}
semaphore.acquire();
try {
diff --git a/core/src/main/java/hudson/triggers/SCMTrigger.java b/core/src/main/java/hudson/triggers/SCMTrigger.java
index e35a3b66af69..43fdc6bb1062 100644
--- a/core/src/main/java/hudson/triggers/SCMTrigger.java
+++ b/core/src/main/java/hudson/triggers/SCMTrigger.java
@@ -468,7 +468,7 @@ public String getUrlName() {
*/
public void doPollingLog(StaplerRequest req, StaplerResponse rsp) throws IOException {
rsp.setContentType("text/plain;charset=UTF-8");
- try (OutputStream os = rsp.getCompressedOutputStream(req);
+ try (OutputStream os = rsp.getOutputStream();
// Prevent jelly from flushing stream so Content-Length header can be added afterwards
FlushProofOutputStream out = new FlushProofOutputStream(os)) {
getPollingLogText().writeLogTo(0, out);
diff --git a/core/src/main/java/hudson/util/ArgumentListBuilder.java b/core/src/main/java/hudson/util/ArgumentListBuilder.java
index 245f3e74a99b..9b2c7bebb594 100644
--- a/core/src/main/java/hudson/util/ArgumentListBuilder.java
+++ b/core/src/main/java/hudson/util/ArgumentListBuilder.java
@@ -295,7 +295,7 @@ public List toList() {
public String toStringWithQuote() {
StringBuilder buf = new StringBuilder();
for (String arg : args) {
- if (buf.length() > 0) buf.append(' ');
+ if (!buf.isEmpty()) buf.append(' ');
if (arg.indexOf(' ') >= 0 || arg.isEmpty())
buf.append('"').append(arg).append('"');
@@ -400,7 +400,7 @@ private static boolean startQuoting(StringBuilder buf, String arg, int atIndex)
* @return true if there are any masked arguments; false otherwise
*/
public boolean hasMaskedArguments() {
- return mask.length() > 0;
+ return !mask.isEmpty();
}
/**
@@ -437,7 +437,7 @@ public String toString() {
if (mask.get(i))
arg = "******";
- if (buf.length() > 0) buf.append(' ');
+ if (!buf.isEmpty()) buf.append(' ');
if (arg.indexOf(' ') >= 0 || arg.isEmpty())
buf.append('"').append(arg).append('"');
diff --git a/core/src/main/java/hudson/util/BootFailure.java b/core/src/main/java/hudson/util/BootFailure.java
index a460ffea148e..f23cf7fa66b7 100644
--- a/core/src/main/java/hudson/util/BootFailure.java
+++ b/core/src/main/java/hudson/util/BootFailure.java
@@ -15,6 +15,7 @@
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.ServletContext;
+import jenkins.model.Jenkins;
import jenkins.util.groovy.GroovyHookScript;
import org.kohsuke.stapler.WebApp;
@@ -51,6 +52,7 @@ public void publish(ServletContext context, @CheckForNull File home) {
.bind("servletContext", context)
.bind("attempts", loadAttempts(home))
.run();
+ Jenkins.get().getLifecycle().onBootFailure(this);
}
/**
diff --git a/core/src/main/java/hudson/util/CompressedFile.java b/core/src/main/java/hudson/util/CompressedFile.java
index d7f42ca7a9ae..cd08f838a250 100644
--- a/core/src/main/java/hudson/util/CompressedFile.java
+++ b/core/src/main/java/hudson/util/CompressedFile.java
@@ -24,8 +24,6 @@
package hudson.util;
-import com.jcraft.jzlib.GZIPInputStream;
-import com.jcraft.jzlib.GZIPOutputStream;
import hudson.Util;
import java.io.File;
import java.io.FileNotFoundException;
@@ -43,6 +41,8 @@
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
/**
* Represents write-once read-many file that can be optionally compressed
diff --git a/core/src/main/java/hudson/util/DescribableList.java b/core/src/main/java/hudson/util/DescribableList.java
index a73dac196b2c..9c62af2f191a 100644
--- a/core/src/main/java/hudson/util/DescribableList.java
+++ b/core/src/main/java/hudson/util/DescribableList.java
@@ -219,8 +219,7 @@ public void rebuildHetero(StaplerRequest req, JSONObject formData, Collection
*/
public void buildDependencyGraph(AbstractProject owner, DependencyGraph graph) {
for (Object o : this) {
- if (o instanceof DependencyDeclarer) {
- DependencyDeclarer dd = (DependencyDeclarer) o;
+ if (o instanceof DependencyDeclarer dd) {
try {
dd.buildDependencyGraph(owner, graph);
} catch (RuntimeException e) {
diff --git a/core/src/main/java/hudson/util/MaskingClassLoader.java b/core/src/main/java/hudson/util/MaskingClassLoader.java
index 74bbbe1a9863..dfad754fc170 100644
--- a/core/src/main/java/hudson/util/MaskingClassLoader.java
+++ b/core/src/main/java/hudson/util/MaskingClassLoader.java
@@ -59,7 +59,7 @@ public MaskingClassLoader(ClassLoader parent, String... masks) {
}
public MaskingClassLoader(ClassLoader parent, Collection masks) {
- super(parent);
+ super("Masking ClassLoader of " + parent.getName(), parent);
this.masksClasses = List.copyOf(masks);
/*
diff --git a/core/src/main/java/hudson/util/MultipartFormDataParser.java b/core/src/main/java/hudson/util/MultipartFormDataParser.java
index 8633a77e1534..f173cff9802b 100644
--- a/core/src/main/java/hudson/util/MultipartFormDataParser.java
+++ b/core/src/main/java/hudson/util/MultipartFormDataParser.java
@@ -27,17 +27,21 @@
import edu.umd.cs.findbugs.annotations.CheckForNull;
import java.io.File;
import java.io.IOException;
+import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
-import org.apache.commons.fileupload.FileCountLimitExceededException;
-import org.apache.commons.fileupload.FileItem;
-import org.apache.commons.fileupload.FileUploadBase;
-import org.apache.commons.fileupload.FileUploadException;
-import org.apache.commons.fileupload.disk.DiskFileItemFactory;
-import org.apache.commons.fileupload.servlet.ServletFileUpload;
+import org.apache.commons.fileupload2.core.DiskFileItem;
+import org.apache.commons.fileupload2.core.DiskFileItemFactory;
+import org.apache.commons.fileupload2.core.FileItem;
+import org.apache.commons.fileupload2.core.FileUploadByteCountLimitException;
+import org.apache.commons.fileupload2.core.FileUploadException;
+import org.apache.commons.fileupload2.core.FileUploadFileCountLimitException;
+import org.apache.commons.fileupload2.core.FileUploadSizeException;
+import org.apache.commons.fileupload2.javax.JavaxServletDiskFileUpload;
+import org.apache.commons.fileupload2.javax.JavaxServletFileUpload;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
@@ -52,7 +56,7 @@ public class MultipartFormDataParser implements AutoCloseable {
/**
* Limits the number of form fields that can be processed in one multipart/form-data request.
- * Used to set {@link org.apache.commons.fileupload.servlet.ServletFileUpload#setFileCountMax(long)}.
+ * Used to set {@link org.apache.commons.fileupload2.javax.JavaxServletFileUpload#setFileCountMax(long)}.
* Despite the name, this applies to all form fields, not just actual file attachments.
* Set to {@code -1} to disable limits.
*/
@@ -60,7 +64,7 @@ public class MultipartFormDataParser implements AutoCloseable {
/**
* Limits the size (in bytes) of individual fields that can be processed in one multipart/form-data request.
- * Used to set {@link org.apache.commons.fileupload.servlet.ServletFileUpload#setFileSizeMax(long)}.
+ * Used to set {@link org.apache.commons.fileupload2.javax.JavaxServletFileUpload#setFileSizeMax(long)}.
* Despite the name, this applies to all form fields, not just actual file attachments.
* Set to {@code -1} to disable limits.
*/
@@ -68,7 +72,7 @@ public class MultipartFormDataParser implements AutoCloseable {
/**
* Limits the total request size (in bytes) that can be processed in one multipart/form-data request.
- * Used to set {@link org.apache.commons.fileupload.servlet.ServletFileUpload#setSizeMax(long)}.
+ * Used to set {@link org.apache.commons.fileupload2.javax.JavaxServletFileUpload#setSizeMax(long)}.
* Set to {@code -1} to disable limits.
*/
private static /* nonfinal for Jenkins script console */ long FILEUPLOAD_MAX_SIZE = Long.getLong(MultipartFormDataParser.class.getName() + ".FILEUPLOAD_MAX_SIZE", -1);
@@ -82,20 +86,20 @@ public MultipartFormDataParser(HttpServletRequest request, int maxParts, long ma
throw new ServletException("Error creating temporary directory", e);
}
tmpDir.deleteOnExit();
- ServletFileUpload upload = new ServletFileUpload(new DiskFileItemFactory(DiskFileItemFactory.DEFAULT_SIZE_THRESHOLD, tmpDir));
+ JavaxServletFileUpload upload = new JavaxServletDiskFileUpload(DiskFileItemFactory.builder().setFile(tmpDir).get());
upload.setFileCountMax(maxParts);
upload.setFileSizeMax(maxPartSize);
upload.setSizeMax(maxSize);
try {
for (FileItem fi : upload.parseRequest(request))
byName.put(fi.getFieldName(), fi);
- } catch (FileCountLimitExceededException e) {
+ } catch (FileUploadFileCountLimitException e) {
throw new ServletException("File upload field count limit exceeded. Consider setting the Java system property "
+ MultipartFormDataParser.class.getName() + ".FILEUPLOAD_MAX_FILES to a value greater than " + FILEUPLOAD_MAX_FILES + ", or to -1 to disable this limit.", e);
- } catch (FileUploadBase.FileSizeLimitExceededException e) {
+ } catch (FileUploadByteCountLimitException e) {
throw new ServletException("File upload field size limit exceeded. Consider setting the Java system property "
+ MultipartFormDataParser.class.getName() + ".FILEUPLOAD_MAX_FILE_SIZE to a value greater than " + FILEUPLOAD_MAX_FILE_SIZE + ", or to -1 to disable this limit.", e);
- } catch (FileUploadBase.SizeLimitExceededException e) {
+ } catch (FileUploadSizeException e) {
throw new ServletException("File upload total size limit exceeded. Consider setting the Java system property "
+ MultipartFormDataParser.class.getName() + ".FILEUPLOAD_MAX_SIZE to a value greater than " + FILEUPLOAD_MAX_SIZE + ", or to -1 to disable this limit.", e);
} catch (FileUploadException e) {
@@ -118,17 +122,30 @@ public String get(String key) {
return fi.getString();
}
- public FileItem getFileItem(String key) {
+ public FileItem getFileItem2(String key) {
return byName.get(key);
}
+ /**
+ * @deprecated use {@link #getFileItem2(String)}
+ */
+ @Deprecated
+ public org.apache.commons.fileupload.FileItem getFileItem(String key) {
+ return org.apache.commons.fileupload.FileItem.fromFileUpload2FileItem(getFileItem2(key));
+ }
+
/**
* If any file is created on the disk, delete them all.
* Even if this method is not called, the resource will be still cleaned up later by GC.
*/
public void cleanUp() {
- for (FileItem item : byName.values())
- item.delete();
+ for (FileItem item : byName.values()) {
+ try {
+ item.delete();
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
}
/** Alias for {@link #cleanUp}. */
diff --git a/core/src/main/java/hudson/util/ProcessTree.java b/core/src/main/java/hudson/util/ProcessTree.java
index 8fbb80c8a8db..21e811a877fd 100644
--- a/core/src/main/java/hudson/util/ProcessTree.java
+++ b/core/src/main/java/hudson/util/ProcessTree.java
@@ -455,7 +455,7 @@ public static ProcessTree get() {
}
// Null-check in case the previous call worked
- boolean vetoes = vetoersExist == null ? true : vetoersExist;
+ boolean vetoes = vetoersExist == null || vetoersExist;
try {
if (File.pathSeparatorChar == ';')
@@ -2115,12 +2115,12 @@ public T act(ProcessCallable callable) throws IOException, InterruptedExc
}
/*
- On MacOS X, there's no procfs
- instead you'd do it with the sysctl
-
+ On MacOS X, there's no procfs
+ instead you'd do it with sysctl
+
There's CLI but that doesn't seem to offer the access to per-process info
-
+
diff --git a/core/src/main/java/hudson/util/RemotingDiagnostics.java b/core/src/main/java/hudson/util/RemotingDiagnostics.java
index 1756d2933b18..06a42beee69d 100644
--- a/core/src/main/java/hudson/util/RemotingDiagnostics.java
+++ b/core/src/main/java/hudson/util/RemotingDiagnostics.java
@@ -217,7 +217,7 @@ public void doHeapDump(StaplerRequest req, StaplerResponse rsp) throws IOExcepti
FilePath dump = obtain();
try {
- dump.copyTo(rsp.getCompressedOutputStream(req));
+ dump.copyTo(rsp.getOutputStream());
} finally {
dump.delete();
}
diff --git a/core/src/main/java/hudson/util/SecretRewriter.java b/core/src/main/java/hudson/util/SecretRewriter.java
deleted file mode 100644
index 4e5015852353..000000000000
--- a/core/src/main/java/hudson/util/SecretRewriter.java
+++ /dev/null
@@ -1,213 +0,0 @@
-package hudson.util;
-
-import hudson.Functions;
-import hudson.Util;
-import hudson.model.TaskListener;
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.PrintWriter;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.InvalidPathException;
-import java.security.GeneralSecurityException;
-import java.security.InvalidKeyException;
-import java.util.Base64;
-import java.util.HashSet;
-import java.util.Set;
-import javax.crypto.Cipher;
-import javax.crypto.SecretKey;
-
-/**
- * Rewrites XML files by looking for Secrets that are stored with the old key and replaces them
- * by the new encrypted values.
- *
- * @author Kohsuke Kawaguchi
- */
-public class SecretRewriter {
- private final Cipher cipher;
- private final SecretKey key;
-
- /**
- * How many files have been scanned?
- */
- private int count;
-
- /**
- * Canonical paths of the directories we are recursing to protect
- * against symlink induced cycles.
- */
- private Set callstack = new HashSet<>();
-
- public SecretRewriter() throws GeneralSecurityException {
- cipher = Secret.getCipher("AES");
- key = HistoricalSecrets.getLegacyKey();
- }
-
- /** @deprecated SECURITY-376: {@code backupDirectory} is ignored */
- @Deprecated
- public SecretRewriter(File backupDirectory) throws GeneralSecurityException {
- this();
- }
-
- private String tryRewrite(String s) throws InvalidKeyException {
- if (s.length() < 24)
- return s; // Encrypting "" in Secret produces 24-letter characters, so this must be the minimum length
- if (!isBase64(s))
- return s; // decode throws IOException if the input is not base64, and this is also a very quick way to filter
-
- byte[] in;
- try {
- in = Base64.getDecoder().decode(s.getBytes(StandardCharsets.UTF_8));
- } catch (IllegalArgumentException e) {
- return s; // not a valid base64
- }
- cipher.init(Cipher.DECRYPT_MODE, key);
- Secret sec = HistoricalSecrets.tryDecrypt(cipher, in);
- if (sec != null) // matched
- return sec.getEncryptedValue(); // replace by the new encrypted value
- else // not encrypted with the legacy key. leave it unmodified
- return s;
- }
-
- /** @deprecated SECURITY-376: {@code backup} is ignored */
- @Deprecated
- public boolean rewrite(File f, File backup) throws InvalidKeyException, IOException {
- return rewrite(f);
- }
-
- public boolean rewrite(File f) throws InvalidKeyException, IOException {
-
- AtomicFileWriter w = new AtomicFileWriter(f.toPath(), StandardCharsets.UTF_8);
- try {
- boolean modified = false; // did we actually change anything?
- try (PrintWriter out = new PrintWriter(new BufferedWriter(w));
- InputStream fin = Files.newInputStream(Util.fileToPath(f));
- BufferedReader r = new BufferedReader(new InputStreamReader(fin, StandardCharsets.UTF_8))) {
- String line;
- StringBuilder buf = new StringBuilder();
-
- while ((line = r.readLine()) != null) {
- int copied = 0;
- buf.setLength(0);
- while (true) {
- int sidx = line.indexOf('>', copied);
- if (sidx < 0) break;
- int eidx = line.indexOf('<', sidx);
- if (eidx < 0) break;
-
- String elementText = line.substring(sidx + 1, eidx);
- String replacement = tryRewrite(elementText);
- if (!replacement.equals(elementText))
- modified = true;
-
- buf.append(line, copied, sidx + 1);
- buf.append(replacement);
- copied = eidx;
- }
- buf.append(line.substring(copied));
- out.println(buf);
- }
- }
-
- if (modified) {
- w.commit();
- }
- return modified;
- } finally {
- w.abort();
- }
- }
-
-
- /**
- * Recursively scans and rewrites a directory.
- *
- * This method shouldn't abort just because one file fails to rewrite.
- *
- * @return
- * Number of files that were actually rewritten.
- */
- // synchronized to prevent accidental concurrent use. this instance is not thread safe
- public synchronized int rewriteRecursive(File dir, TaskListener listener) throws InvalidKeyException {
- return rewriteRecursive(dir, "", listener);
- }
-
- private int rewriteRecursive(File dir, String relative, TaskListener listener) throws InvalidKeyException {
- String canonical;
- try {
- canonical = dir.toPath().toRealPath().toString();
- } catch (IOException | InvalidPathException e) {
- canonical = dir.getAbsolutePath(); //
- }
- if (!callstack.add(canonical)) {
- listener.getLogger().println("Cycle detected: " + dir);
- return 0;
- }
-
- try {
- File[] children = dir.listFiles();
- if (children == null) return 0;
-
- int rewritten = 0;
- for (File child : children) {
- String cn = child.getName();
- if (cn.endsWith(".xml")) {
- if (count++ % 100 == 0)
- listener.getLogger().println("Scanning " + child);
- try {
- if (rewrite(child)) {
- listener.getLogger().println("Rewritten " + child);
- rewritten++;
- }
- } catch (IOException e) {
- Functions.printStackTrace(e, listener.error("Failed to rewrite " + child));
- }
- }
- if (child.isDirectory()) {
- if (!isIgnoredDir(child))
- rewritten += rewriteRecursive(child,
- relative.isEmpty() ? cn : relative + '/' + cn,
- listener);
- }
- }
- return rewritten;
- } finally {
- callstack.remove(canonical);
- }
- }
-
- /**
- * Decides if this directory is worth visiting or not.
- */
- protected boolean isIgnoredDir(File dir) {
- // ignoring the workspace and the artifacts directories. Both of them
- // are potentially large and they do not store any secrets.
- String n = dir.getName();
- return n.equals("workspace") || n.equals("artifacts")
- || n.equals("plugins") // no mutable data here
- || n.equals(".") || n.equals("..");
- }
-
- private static boolean isBase64(char ch) {
- return ch < 128 && IS_BASE64[ch];
- }
-
- private static boolean isBase64(String s) {
- for (int i = 0; i < s.length(); i++)
- if (!isBase64(s.charAt(i)))
- return false;
- return true;
- }
-
- private static final boolean[] IS_BASE64 = new boolean[128];
-
- static {
- String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
- for (int i = 0; i < chars.length(); i++)
- IS_BASE64[chars.charAt(i)] = true;
- }
-}
diff --git a/core/src/main/java/hudson/util/StackedAreaRenderer2.java b/core/src/main/java/hudson/util/StackedAreaRenderer2.java
index 3d165b27cce9..19fefcd65d9b 100644
--- a/core/src/main/java/hudson/util/StackedAreaRenderer2.java
+++ b/core/src/main/java/hudson/util/StackedAreaRenderer2.java
@@ -109,7 +109,7 @@ public void drawItem(Graphics2D g2,
double value = dataValue.doubleValue();
- // leave the y values (y1, y0) untranslated as it is going to be be
+ // leave the y values (y1, y0) untranslated as it is going to be
// stacked up later by previous series values, after this it will be
// translated.
double xx1 = domainAxis.getCategoryMiddle(column, getColumnCount(),
diff --git a/core/src/main/java/hudson/util/XStream2.java b/core/src/main/java/hudson/util/XStream2.java
index cf1b5e9bcaab..774008e5e694 100644
--- a/core/src/main/java/hudson/util/XStream2.java
+++ b/core/src/main/java/hudson/util/XStream2.java
@@ -469,7 +469,7 @@ public Class realClass(String elementName) {
*/
private static final class AssociatedConverterImpl implements Converter {
private final XStream xstream;
- private static final ClassValue> classCache = new ClassValue>() {
+ private static final ClassValue> classCache = new ClassValue<>() {
@Override
protected Class extends ConverterMatcher> computeValue(Class> type) {
return computeConverterClass(type);
diff --git a/core/src/main/java/hudson/util/io/TarArchiver.java b/core/src/main/java/hudson/util/io/TarArchiver.java
index 00b3d1b55d12..597b4ff47339 100644
--- a/core/src/main/java/hudson/util/io/TarArchiver.java
+++ b/core/src/main/java/hudson/util/io/TarArchiver.java
@@ -36,10 +36,10 @@
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.attribute.BasicFileAttributes;
-import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
-import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
-import org.apache.commons.compress.archivers.tar.TarConstants;
-import org.apache.commons.compress.utils.BoundedInputStream;
+import org.apache.commons.io.input.BoundedInputStream;
+import org.apache.tools.tar.TarConstants;
+import org.apache.tools.tar.TarEntry;
+import org.apache.tools.tar.TarOutputStream;
/**
* {@link FileVisitor} that creates a tar archive.
@@ -48,17 +48,17 @@
*/
final class TarArchiver extends Archiver {
private final byte[] buf = new byte[8192];
- private final TarArchiveOutputStream tar;
+ private final TarOutputStream tar;
TarArchiver(OutputStream out, Charset filenamesEncoding) {
- tar = new TarArchiveOutputStream(out, filenamesEncoding.name());
- tar.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_STAR);
- tar.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
+ tar = new TarOutputStream(out, filenamesEncoding.name());
+ tar.setBigNumberMode(TarOutputStream.BIGNUMBER_STAR);
+ tar.setLongFileMode(TarOutputStream.LONGFILE_GNU);
}
@Override
public void visitSymlink(File link, String target, String relativePath) throws IOException {
- TarArchiveEntry e = new TarArchiveEntry(relativePath, TarConstants.LF_SYMLINK);
+ TarEntry e = new TarEntry(relativePath, TarConstants.LF_SYMLINK);
try {
int mode = IOUtils.mode(link);
if (mode != -1) {
@@ -70,8 +70,8 @@ public void visitSymlink(File link, String target, String relativePath) throws I
e.setLinkName(target);
- tar.putArchiveEntry(e);
- tar.closeArchiveEntry();
+ tar.putNextEntry(e);
+ tar.closeEntry();
entriesWritten++;
}
@@ -88,7 +88,7 @@ public void visit(File file, String relativePath) throws IOException {
BasicFileAttributes basicFileAttributes = Files.readAttributes(Util.fileToPath(file), BasicFileAttributes.class);
if (basicFileAttributes.isDirectory())
relativePath += '/';
- TarArchiveEntry te = new TarArchiveEntry(relativePath);
+ TarEntry te = new TarEntry(relativePath);
int mode = IOUtils.mode(file);
if (mode != -1) te.setMode(mode);
te.setModTime(basicFileAttributes.lastModifiedTime().toMillis());
@@ -98,7 +98,7 @@ public void visit(File file, String relativePath) throws IOException {
size = basicFileAttributes.size();
te.setSize(size);
}
- tar.putArchiveEntry(te);
+ tar.putNextEntry(te);
try {
if (!basicFileAttributes.isDirectory()) {
// ensure we don't write more bytes than the declared when we created the entry
@@ -118,7 +118,7 @@ public void visit(File file, String relativePath) throws IOException {
}
}
} finally { // always close the entry
- tar.closeArchiveEntry();
+ tar.closeEntry();
}
entriesWritten++;
}
diff --git a/core/src/main/java/hudson/views/ListViewColumn.java b/core/src/main/java/hudson/views/ListViewColumn.java
index 1796f4cc495c..91e93e2145da 100644
--- a/core/src/main/java/hudson/views/ListViewColumn.java
+++ b/core/src/main/java/hudson/views/ListViewColumn.java
@@ -157,8 +157,7 @@ private static List createDefaultInitialColumnList(List d : descriptors)
try {
- if (d instanceof ListViewColumnDescriptor) {
- ListViewColumnDescriptor ld = (ListViewColumnDescriptor) d;
+ if (d instanceof ListViewColumnDescriptor ld) {
if (!ld.shownByDefault()) {
continue; // skip this
}
diff --git a/core/src/main/java/jenkins/agents/CloudSet.java b/core/src/main/java/jenkins/agents/CloudSet.java
index e69a3f9add3c..163556a001d1 100644
--- a/core/src/main/java/jenkins/agents/CloudSet.java
+++ b/core/src/main/java/jenkins/agents/CloudSet.java
@@ -41,7 +41,6 @@
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.logging.Level;
@@ -131,7 +130,7 @@ public String getCloudUpdateCenterCategoryLabel() {
@Override
public ModelObjectWithContextMenu.ContextMenu doChildrenContextMenu(StaplerRequest request, StaplerResponse response) throws Exception {
ModelObjectWithContextMenu.ContextMenu m = new ModelObjectWithContextMenu.ContextMenu();
- Jenkins.get().clouds.stream().forEach(c -> m.add(c));
+ Jenkins.get().clouds.stream().forEach(m::add);
return m;
}
@@ -242,9 +241,13 @@ private void handleNewCloudPage(Descriptor descriptor, String name, Stapl
*/
@POST
public synchronized void doDoCreate(StaplerRequest req, StaplerResponse rsp,
- @QueryParameter String type) throws IOException, ServletException, Descriptor.FormException {
+ @QueryParameter String cloudDescriptorName) throws IOException, ServletException, Descriptor.FormException {
Jenkins.get().checkPermission(Jenkins.ADMINISTER);
- Cloud cloud = Cloud.all().find(type).newInstance(req, req.getSubmittedForm());
+ Descriptor cloudDescriptor = Cloud.all().findByName(cloudDescriptorName);
+ if (cloudDescriptor == null) {
+ throw new Failure(String.format("No cloud type ‘%s’ is known", cloudDescriptorName));
+ }
+ Cloud cloud = cloudDescriptor.newInstance(req, req.getSubmittedForm());
if (!Jenkins.get().clouds.add(cloud)) {
LOGGER.log(Level.WARNING, () -> "Creating duplicate cloud name " + cloud.name + ". Plugin " + Jenkins.get().getPluginManager().whichPlugin(cloud.getClass()) + " should be updated to support user provided name.");
}
@@ -261,7 +264,7 @@ public void doReorder(StaplerRequest req, StaplerResponse rsp) throws IOExceptio
}
var namesList = Arrays.asList(names);
var clouds = new ArrayList<>(Jenkins.get().clouds);
- Collections.sort(clouds, Comparator.comparingInt(c -> getIndexOf(namesList, c)));
+ clouds.sort(Comparator.comparingInt(c -> getIndexOf(namesList, c)));
Jenkins.get().clouds.replaceBy(clouds);
rsp.sendRedirect2(".");
}
diff --git a/core/src/main/java/jenkins/agents/WebSocketAgents.java b/core/src/main/java/jenkins/agents/WebSocketAgents.java
index 005587c6365a..d9560f156ebd 100644
--- a/core/src/main/java/jenkins/agents/WebSocketAgents.java
+++ b/core/src/main/java/jenkins/agents/WebSocketAgents.java
@@ -36,6 +36,7 @@
import hudson.remoting.ChannelBuilder;
import hudson.remoting.ChunkHeader;
import hudson.remoting.Engine;
+import hudson.slaves.SlaveComputer;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
@@ -107,7 +108,9 @@ public HttpResponse doIndex(StaplerRequest req, StaplerResponse rsp) throws IOEx
Capability remoteCapability = Capability.fromASCII(remoteCapabilityStr);
LOGGER.fine(() -> "received " + remoteCapability);
rsp.setHeader(Capability.KEY, new Capability().toASCII());
- rsp.setHeader(Engine.REMOTING_MINIMUM_VERSION_HEADER, RemotingVersionInfo.getMinimumSupportedVersion().toString());
+ if (!SlaveComputer.ALLOW_UNSUPPORTED_REMOTING_VERSIONS) {
+ rsp.setHeader(Engine.REMOTING_MINIMUM_VERSION_HEADER, RemotingVersionInfo.getMinimumSupportedVersion().toString());
+ }
rsp.setHeader(Engine.WEBSOCKET_COOKIE_HEADER, cookie);
return WebSockets.upgrade(new Session(state, agent, remoteCapability));
}
diff --git a/core/src/main/java/jenkins/diagnostics/URICheckEncodingMonitor.java b/core/src/main/java/jenkins/diagnostics/URICheckEncodingMonitor.java
index 3563699b19b7..9fdd7bc38436 100644
--- a/core/src/main/java/jenkins/diagnostics/URICheckEncodingMonitor.java
+++ b/core/src/main/java/jenkins/diagnostics/URICheckEncodingMonitor.java
@@ -7,6 +7,7 @@
import hudson.model.AdministrativeMonitor;
import hudson.util.FormValidation;
import java.io.IOException;
+import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -22,7 +23,7 @@ public class URICheckEncodingMonitor extends AdministrativeMonitor {
private static final Logger LOGGER = Logger.getLogger(URICheckEncodingMonitor.class.getName());
public boolean isCheckEnabled() {
- return !"ISO-8859-1".equalsIgnoreCase(System.getProperty("file.encoding"));
+ return !"ISO-8859-1".equalsIgnoreCase(Charset.defaultCharset().displayName());
}
@Override
diff --git a/core/src/main/java/jenkins/fingerprints/FileFingerprintStorage.java b/core/src/main/java/jenkins/fingerprints/FileFingerprintStorage.java
index d3154e6db6a3..409788ba5706 100644
--- a/core/src/main/java/jenkins/fingerprints/FileFingerprintStorage.java
+++ b/core/src/main/java/jenkins/fingerprints/FileFingerprintStorage.java
@@ -101,11 +101,10 @@ public FileFingerprintStorage() {}
try {
Object loaded = configFile.read();
- if (!(loaded instanceof Fingerprint)) {
+ if (!(loaded instanceof Fingerprint f)) {
throw new IOException("Unexpected Fingerprint type. Expected " + Fingerprint.class + " or subclass but got "
+ (loaded != null ? loaded.getClass() : "null"));
}
- Fingerprint f = (Fingerprint) loaded;
if (f.getPersistedFacets() == null) {
logger.log(Level.WARNING, "Malformed fingerprint {0}: Missing facets", configFile);
Files.deleteIfExists(Util.fileToPath(file));
diff --git a/core/src/main/java/jenkins/install/InstallUtil.java b/core/src/main/java/jenkins/install/InstallUtil.java
index 3d1bf24e267d..6fb37e20bf3a 100644
--- a/core/src/main/java/jenkins/install/InstallUtil.java
+++ b/core/src/main/java/jenkins/install/InstallUtil.java
@@ -234,7 +234,7 @@ public static void saveLastExecVersion() {
if (configFile.exists()) {
try {
String lastVersion = XMLUtils.getValue("/hudson/version", configFile);
- if (lastVersion.length() > 0) {
+ if (!lastVersion.isEmpty()) {
LOGGER.log(Level.FINE, "discovered serialized lastVersion {0}", lastVersion);
return lastVersion;
}
diff --git a/core/src/main/java/jenkins/management/AdministrativeMonitorsDecorator.java b/core/src/main/java/jenkins/management/AdministrativeMonitorsDecorator.java
index cc1d5214e0ae..5b94090f4e3b 100644
--- a/core/src/main/java/jenkins/management/AdministrativeMonitorsDecorator.java
+++ b/core/src/main/java/jenkins/management/AdministrativeMonitorsDecorator.java
@@ -139,7 +139,7 @@ private Collection getAllActiveAdministrativeMonitors() {
* @return the list of active monitors if we should display them, otherwise null.
*/
public Collection getMonitorsToDisplay() {
- if (!Jenkins.get().hasPermission(Jenkins.SYSTEM_READ)) {
+ if (!(AdministrativeMonitor.hasPermissionToDisplay())) {
return null;
}
@@ -150,7 +150,7 @@ public Collection getMonitorsToDisplay() {
}
List ancestors = req.getAncestors();
- if (ancestors == null || ancestors.size() == 0) {
+ if (ancestors == null || ancestors.isEmpty()) {
// ???
return null;
}
diff --git a/core/src/main/java/jenkins/management/AsynchronousAdministrativeMonitor.java b/core/src/main/java/jenkins/management/AsynchronousAdministrativeMonitor.java
index 7ebc732518ae..2884439681c6 100644
--- a/core/src/main/java/jenkins/management/AsynchronousAdministrativeMonitor.java
+++ b/core/src/main/java/jenkins/management/AsynchronousAdministrativeMonitor.java
@@ -17,7 +17,6 @@
import java.util.logging.Level;
import java.util.logging.Logger;
import jenkins.model.Jenkins;
-import jenkins.security.RekeySecretAdminMonitor;
/**
* Convenient partial implementation of {@link AdministrativeMonitor} that involves a background "fixing" action
@@ -27,9 +26,6 @@
* A subclass defines what that background fixing actually does in {@link #fix(TaskListener)}. The logging output
* from it gets persisted, and this class provides a "/log" view that allows the administrator to monitor its progress.
*
- *
- * See {@link RekeySecretAdminMonitor} for an example of how to subtype this class.
- *
* @author Kohsuke Kawaguchi
*/
public abstract class AsynchronousAdministrativeMonitor extends AdministrativeMonitor {
diff --git a/core/src/main/java/jenkins/model/GlobalComputerRetentionCheckIntervalConfiguration.java b/core/src/main/java/jenkins/model/GlobalComputerRetentionCheckIntervalConfiguration.java
new file mode 100644
index 000000000000..46d760fe8ee2
--- /dev/null
+++ b/core/src/main/java/jenkins/model/GlobalComputerRetentionCheckIntervalConfiguration.java
@@ -0,0 +1,72 @@
+package jenkins.model;
+
+import hudson.Extension;
+import hudson.model.PersistentDescriptor;
+import java.util.logging.Logger;
+import net.sf.json.JSONException;
+import net.sf.json.JSONObject;
+import org.jenkinsci.Symbol;
+import org.kohsuke.stapler.StaplerRequest;
+
+/**
+ * Configures check interval for computer retention.
+ *
+ * @author Jakob Ackermann
+ */
+@Extension(ordinal = 401) @Symbol("computerRetentionCheckInterval")
+public class GlobalComputerRetentionCheckIntervalConfiguration extends GlobalConfiguration implements PersistentDescriptor {
+ /**
+ * For {@link hudson.slaves.ComputerRetentionWork#getRecurrencePeriod()}
+ */
+ private int computerRetentionCheckInterval = 60;
+
+ /**
+ * Gets the check interval for computer retention.
+ *
+ * @since 2.463
+ */
+ public int getComputerRetentionCheckInterval() {
+ if (computerRetentionCheckInterval <= 0) {
+ LOGGER.info("computerRetentionCheckInterval must be greater than zero, falling back to 60s");
+ return 60;
+ }
+ if (computerRetentionCheckInterval > 60) {
+ LOGGER.info("computerRetentionCheckInterval is limited to 60s");
+ return 60;
+ }
+ return computerRetentionCheckInterval;
+ }
+
+ /**
+ * Updates the check interval for computer retention and restarts the check cycle.
+ *
+ * @param interval new check interval in seconds
+ * @throws IllegalArgumentException interval must be greater than zero
+ * @since 2.463
+ */
+ private void setComputerRetentionCheckInterval(int interval) throws IllegalArgumentException {
+ if (interval <= 0) {
+ throw new IllegalArgumentException("interval must be greater than zero");
+ }
+ if (interval > 60) {
+ throw new IllegalArgumentException("interval must be below or equal 60s");
+ }
+ computerRetentionCheckInterval = interval;
+ save();
+ }
+
+ @Override
+ public boolean configure(StaplerRequest req, JSONObject json) throws FormException {
+ try {
+ final int interval = json.getInt("computerRetentionCheckInterval");
+ setComputerRetentionCheckInterval(interval);
+ return true;
+ } catch (IllegalArgumentException e) {
+ throw new FormException(e, "computerRetentionCheckInterval");
+ } catch (JSONException e) {
+ throw new FormException(e.getMessage(), "computerRetentionCheckInterval");
+ }
+ }
+
+ private static final Logger LOGGER = Logger.getLogger(GlobalComputerRetentionCheckIntervalConfiguration.class.getName());
+}
diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java
index 77e6f69ec875..a648935527ae 100644
--- a/core/src/main/java/jenkins/model/Jenkins.java
+++ b/core/src/main/java/jenkins/model/Jenkins.java
@@ -183,6 +183,7 @@
import hudson.triggers.TriggerDescriptor;
import hudson.util.AdministrativeError;
import hudson.util.ClockDifference;
+import hudson.util.ComboBoxModel;
import hudson.util.CopyOnWriteList;
import hudson.util.CopyOnWriteMap;
import hudson.util.DaemonThreadFactory;
@@ -499,7 +500,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
* STARTUP_MARKER_FILE.get(); // returns false if we are on a fresh startup. True for next startups.
* }
*/
- private static transient FileBoolean STARTUP_MARKER_FILE;
+ private static FileBoolean STARTUP_MARKER_FILE;
private volatile List jdks = new ArrayList<>();
@@ -1908,6 +1909,11 @@ public Collection getJobNames() {
return names;
}
+ @Restricted(NoExternalUse.class)
+ public ComboBoxModel doFillJobNameItems() {
+ return new ComboBoxModel(getJobNames());
+ }
+
@Override
public List getViewActions() {
return getActions();
@@ -2355,12 +2361,12 @@ public AdministrativeMonitor getAdministrativeMonitor(String id) {
* @since 2.64
*/
public List getActiveAdministrativeMonitors() {
- if (!Jenkins.get().hasPermission(SYSTEM_READ)) {
+ if (!AdministrativeMonitor.hasPermissionToDisplay()) {
return Collections.emptyList();
}
return administrativeMonitors.stream().filter(m -> {
try {
- return Jenkins.get().hasPermission(m.getRequiredPermission()) && m.isEnabled() && m.isActivated();
+ return m.hasRequiredPermission() && m.isEnabled() && m.isActivated();
} catch (Throwable x) {
LOGGER.log(Level.WARNING, null, x);
return false;
@@ -2897,13 +2903,16 @@ public ExtensionList getExtensionList(String extensionType) throws ClassNotFound
*/
public void refreshExtensions() throws ExtensionRefreshException {
ExtensionList finders = getExtensionList(ExtensionFinder.class);
+ LOGGER.finer(() -> "refreshExtensions " + finders);
for (ExtensionFinder ef : finders) {
if (!ef.isRefreshable())
throw new ExtensionRefreshException(ef + " doesn't support refresh");
}
List fragments = new ArrayList<>();
+
for (ExtensionFinder ef : finders) {
+ LOGGER.finer(() -> "searching " + ef);
fragments.add(ef.refresh());
}
ExtensionComponentSet delta = ExtensionComponentSet.union(fragments).filtered();
@@ -2912,12 +2921,21 @@ public void refreshExtensions() throws ExtensionRefreshException {
List> newFinders = new ArrayList<>(delta.find(ExtensionFinder.class));
while (!newFinders.isEmpty()) {
ExtensionFinder f = newFinders.remove(newFinders.size() - 1).getInstance();
+ LOGGER.finer(() -> "found new ExtensionFinder " + f);
ExtensionComponentSet ecs = ExtensionComponentSet.allOf(f).filtered();
newFinders.addAll(ecs.find(ExtensionFinder.class));
delta = ExtensionComponentSet.union(delta, ecs);
}
+ // we may not have found a new Extension finder but we may be using an extension finder that is extensible
+ // e.g. hudson.ExtensionFinder.GuiceFinder is extensible by GuiceExtensionAnnotation which is done by the variant plugin
+ // so lets give it one more chance.
+ for (ExtensionFinder ef : finders) {
+ LOGGER.finer(() -> "searching again in " + ef);
+ delta = ExtensionComponentSet.union(delta, ef.refresh().filtered());
+ }
+
for (ExtensionList el : extensionLists.values()) {
el.refresh(delta);
}
@@ -3094,8 +3112,7 @@ public Item getItem(String pathName, ItemGroup context) {
continue;
}
- if (ctx instanceof ItemGroup) {
- ItemGroup g = (ItemGroup) ctx;
+ if (ctx instanceof ItemGroup g) {
Item i = g.getItem(s);
if (i == null || !i.hasPermission(Item.READ)) { // TODO consider DISCOVER
ctx = null; // can't go up further
@@ -3465,10 +3482,9 @@ private void setBuildsAndWorkspacesDir() throws IOException, InvalidBuildsDir {
File d = new File(replacedValue);
if (!d.isDirectory()) {
// if dir does not exist (almost guaranteed) need to make sure nearest existing ancestor can be written to
- d = d.getParentFile();
- while (!d.exists()) {
+ do {
d = d.getParentFile();
- }
+ } while (!d.exists());
if (!d.canWrite()) {
throw new InvalidBuildsDir(newBuildsDirValue + " does not exist and probably cannot be created");
}
@@ -4480,7 +4496,7 @@ public void doDoFingerprintCheck(StaplerRequest req, StaplerResponse rsp) throws
rsp.sendError(HttpServletResponse.SC_FORBIDDEN, "No crumb found");
}
rsp.sendRedirect2(req.getContextPath() + "/fingerprint/" +
- Util.getDigestOf(p.getFileItem("name").getInputStream()) + '/');
+ Util.getDigestOf(p.getFileItem2("name").getInputStream()) + '/');
}
}
diff --git a/core/src/main/java/jenkins/model/ParameterizedJobMixIn.java b/core/src/main/java/jenkins/model/ParameterizedJobMixIn.java
index be71a97c7379..662ffa9359e2 100644
--- a/core/src/main/java/jenkins/model/ParameterizedJobMixIn.java
+++ b/core/src/main/java/jenkins/model/ParameterizedJobMixIn.java
@@ -218,6 +218,7 @@ public final void doBuild(StaplerRequest req, StaplerResponse rsp, @QueryParamet
Queue.Item item = Jenkins.get().getQueue().schedule2(asJob(), delay.getTimeInSeconds(), getBuildCause(asJob(), req)).getItem();
if (item != null) {
+ // TODO JENKINS-66105 use SC_SEE_OTHER if !ScheduleResult.created
rsp.sendRedirect(SC_CREATED, req.getContextPath() + '/' + item.getUrl());
} else {
rsp.sendRedirect(".");
@@ -346,7 +347,7 @@ static ParameterizedJob resolveForCLI(@Argument(required = true, metaVar = "NAME
* (Would have been done entirely as an interface with default methods had this been designed for Java 8.)
*/
default ParameterizedJobMixIn getParameterizedJobMixIn() {
- return new ParameterizedJobMixIn() {
+ return new ParameterizedJobMixIn<>() {
@SuppressWarnings("unchecked") // untypable
@Override protected JobT asJob() {
return (JobT) ParameterizedJob.this;
diff --git a/test/src/test/resources/jenkins/security/stapler/StaplerDispatchValidatorTest/Groovy/compress.groovy b/core/src/main/java/jenkins/model/experimentalflags/RemoveYuiUserExperimentalFlag.java
similarity index 60%
rename from test/src/test/resources/jenkins/security/stapler/StaplerDispatchValidatorTest/Groovy/compress.groovy
rename to core/src/main/java/jenkins/model/experimentalflags/RemoveYuiUserExperimentalFlag.java
index 2c342e42bf97..e8f8dcc31775 100644
--- a/test/src/test/resources/jenkins/security/stapler/StaplerDispatchValidatorTest/Groovy/compress.groovy
+++ b/core/src/main/java/jenkins/model/experimentalflags/RemoveYuiUserExperimentalFlag.java
@@ -1,7 +1,7 @@
/*
* The MIT License
*
- * Copyright (c) 2019 CloudBees, Inc.
+ * Copyright (c) 2024, Markus Winter
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -22,13 +22,28 @@
* THE SOFTWARE.
*/
-package jenkins.security.stapler.StaplerDispatchValidatorTest.Groovy
+package jenkins.model.experimentalflags;
-def st = namespace('jelly:stapler')
-def l = namespace('/lib/layout')
-st.compress {
- l.view {
- st.contentType(value: 'text/html')
- st.include(page: 'frag')
+import edu.umd.cs.findbugs.annotations.Nullable;
+import hudson.Extension;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
+
+@Extension
+@Restricted(NoExternalUse.class)
+public class RemoveYuiUserExperimentalFlag extends BooleanUserExperimentalFlag {
+ public RemoveYuiUserExperimentalFlag() {
+ super("remove-yui.flag");
+ }
+
+ @Override
+ public String getDisplayName() {
+ return "Remove YUI";
+ }
+
+ @Nullable
+ @Override
+ public String getShortDescription() {
+ return "Remove YUI from all Jenkins UI pages. This will break anything that depends on YUI";
}
}
diff --git a/core/src/main/java/jenkins/model/experimentalflags/UserExperimentalFlagsProperty.java b/core/src/main/java/jenkins/model/experimentalflags/UserExperimentalFlagsProperty.java
index 6732f2e5d696..85332d26973c 100644
--- a/core/src/main/java/jenkins/model/experimentalflags/UserExperimentalFlagsProperty.java
+++ b/core/src/main/java/jenkins/model/experimentalflags/UserExperimentalFlagsProperty.java
@@ -31,6 +31,7 @@
import hudson.model.User;
import hudson.model.UserProperty;
import hudson.model.UserPropertyDescriptor;
+import hudson.model.userproperty.UserPropertyCategory;
import java.util.HashMap;
import java.util.Map;
import net.sf.json.JSONObject;
@@ -76,13 +77,19 @@ public static final class DescriptorImpl extends UserPropertyDescriptor {
public UserProperty newInstance(@Nullable StaplerRequest req, @NonNull JSONObject formData) throws FormException {
JSONObject flagsObj = formData.getJSONObject("flags");
Map flags = new HashMap<>();
- for (Object key : flagsObj.keySet()) {
- String value = (String) flagsObj.get((String) key);
+ for (String key : flagsObj.keySet()) {
+ String value = (String) flagsObj.get(key);
if (!value.isEmpty()) {
- flags.put((String) key, value);
+ flags.put(key, value);
}
}
return new UserExperimentalFlagsProperty(flags);
}
+
+ @NonNull
+ @Override
+ public UserPropertyCategory getUserPropertyCategory() {
+ return UserPropertyCategory.get(UserPropertyCategory.Experimental.class);
+ }
}
}
diff --git a/core/src/main/java/jenkins/model/lazy/LazyBuildMixIn.java b/core/src/main/java/jenkins/model/lazy/LazyBuildMixIn.java
index 570048c5327f..c73c5cbf80f1 100644
--- a/core/src/main/java/jenkins/model/lazy/LazyBuildMixIn.java
+++ b/core/src/main/java/jenkins/model/lazy/LazyBuildMixIn.java
@@ -39,7 +39,6 @@
import hudson.model.RunMap;
import hudson.model.listeners.ItemListener;
import hudson.model.queue.SubTask;
-import hudson.widgets.BuildHistoryWidget;
import hudson.widgets.HistoryWidget;
import java.io.File;
import java.io.IOException;
@@ -297,10 +296,11 @@ public List getEstimatedDurationCandidates() {
}
/**
- * Suitable for {@link Job#createHistoryWidget}.
+ * @deprecated Remove any code calling this method, history widget is now created via {@link jenkins.widgets.WidgetFactory} implementation.
*/
+ @Deprecated(forRemoval = true, since = "2.459")
public final HistoryWidget createHistoryWidget() {
- return new BuildHistoryWidget(asJob(), builds, Job.HISTORY_ADAPTER);
+ throw new IllegalStateException("HistoryWidget is now created via WidgetFactory implementation");
}
/**
diff --git a/core/src/main/java/jenkins/model/queue/ItemDeletion.java b/core/src/main/java/jenkins/model/queue/ItemDeletion.java
index e5c9fc2ca87e..a2d954fbc459 100644
--- a/core/src/main/java/jenkins/model/queue/ItemDeletion.java
+++ b/core/src/main/java/jenkins/model/queue/ItemDeletion.java
@@ -28,25 +28,42 @@
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.ExtensionList;
+import hudson.model.AbstractItem;
import hudson.model.Action;
+import hudson.model.Computer;
+import hudson.model.Executor;
+import hudson.model.Failure;
import hudson.model.Item;
+import hudson.model.Messages;
import hudson.model.Queue;
+import hudson.model.Result;
+import hudson.model.queue.Executables;
+import hudson.model.queue.SubTask;
import hudson.model.queue.Tasks;
+import hudson.model.queue.WorkUnit;
import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.logging.Logger;
+import jenkins.model.Jenkins;
import net.jcip.annotations.GuardedBy;
/**
* A {@link Queue.QueueDecisionHandler} that blocks items being deleted from entering the queue.
- *
+ * @see AbstractItem#delete()
* @since 2.55
*/
@Extension
public class ItemDeletion extends Queue.QueueDecisionHandler {
+ private static final Logger LOGGER = Logger.getLogger(ItemDeletion.class.getName());
+
/**
* Lock to guard the {@link #registrations} set.
*/
@@ -176,4 +193,94 @@ public boolean shouldSchedule(Queue.Task p, List actions) {
}
return true;
}
+
+ /**
+ * Cancels any builds in progress of this item (if a job) or descendants (if a folder).
+ * Also cancels any associated queue items.
+ * @param initiatingItem an item being deleted
+ * @since 2.470
+ */
+ public static void cancelBuildsInProgress(@NonNull Item initiatingItem) throws Failure, InterruptedException {
+ Queue queue = Queue.getInstance();
+ if (initiatingItem instanceof Queue.Task) {
+ // clear any items in the queue so they do not get picked up
+ queue.cancel((Queue.Task) initiatingItem);
+ }
+ // now cancel any child items - this happens after ItemDeletion registration, so we can use a snapshot
+ for (Queue.Item i : queue.getItems()) {
+ Item item = Tasks.getItemOf(i.task);
+ while (item != null) {
+ if (item == initiatingItem) {
+ if (!queue.cancel(i)) {
+ LOGGER.warning(() -> "failed to cancel " + i);
+ }
+ break;
+ }
+ if (item.getParent() instanceof Item) {
+ item = (Item) item.getParent();
+ } else {
+ break;
+ }
+ }
+ }
+ // interrupt any builds in progress (and this should be a recursive test so that folders do not pay
+ // the 15 second delay for every child item). This happens after queue cancellation, so will be
+ // a complete set of builds in flight
+ Map buildsInProgress = new LinkedHashMap<>();
+ for (Computer c : Jenkins.get().getComputers()) {
+ for (Executor e : c.getAllExecutors()) {
+ final WorkUnit workUnit = e.getCurrentWorkUnit();
+ final Queue.Executable executable = workUnit != null ? workUnit.getExecutable() : null;
+ final SubTask subtask = executable != null ? Executables.getParentOf(executable) : null;
+ if (subtask != null) {
+ Item item = Tasks.getItemOf(subtask);
+ while (item != null) {
+ if (item == initiatingItem) {
+ buildsInProgress.put(e, e.getCurrentExecutable());
+ e.interrupt(Result.ABORTED);
+ break;
+ }
+ if (item.getParent() instanceof Item) {
+ item = (Item) item.getParent();
+ } else {
+ break;
+ }
+ }
+ }
+ }
+ }
+ if (!buildsInProgress.isEmpty()) {
+ // give them 15 seconds or so to respond to the interrupt
+ long expiration = System.nanoTime() + TimeUnit.SECONDS.toNanos(15);
+ // comparison with executor.getCurrentExecutable() == computation currently should always be true
+ // as we no longer recycle Executors, but safer to future-proof in case we ever revisit recycling
+ while (!buildsInProgress.isEmpty() && expiration - System.nanoTime() > 0L) {
+ // we know that ItemDeletion will prevent any new builds in the queue
+ // ItemDeletion happens-before Queue.cancel so we know that the Queue will stay clear
+ // Queue.cancel happens-before collecting the buildsInProgress list
+ // thus buildsInProgress contains the complete set we need to interrupt and wait for
+ for (Iterator> iterator =
+ buildsInProgress.entrySet().iterator();
+ iterator.hasNext(); ) {
+ Map.Entry entry = iterator.next();
+ // comparison with executor.getCurrentExecutable() == executable currently should always be
+ // true as we no longer recycle Executors, but safer to future-proof in case we ever
+ // revisit recycling.
+ if (!entry.getKey().isAlive()
+ || entry.getValue() != entry.getKey().getCurrentExecutable()) {
+ iterator.remove();
+ }
+ // I don't know why, but we have to keep interrupting
+ entry.getKey().interrupt(Result.ABORTED);
+ }
+ Thread.sleep(50L);
+ }
+ if (!buildsInProgress.isEmpty()) {
+ throw new Failure(Messages.AbstractItem_FailureToStopBuilds(
+ buildsInProgress.size(), initiatingItem.getFullDisplayName()
+ ));
+ }
+ }
+ }
+
}
diff --git a/core/src/main/java/jenkins/monitor/JavaVersionRecommendationAdminMonitor.java b/core/src/main/java/jenkins/monitor/JavaVersionRecommendationAdminMonitor.java
index 86093a634617..c95740613765 100644
--- a/core/src/main/java/jenkins/monitor/JavaVersionRecommendationAdminMonitor.java
+++ b/core/src/main/java/jenkins/monitor/JavaVersionRecommendationAdminMonitor.java
@@ -78,7 +78,6 @@ public class JavaVersionRecommendationAdminMonitor extends AdministrativeMonitor
static {
NavigableMap supportedVersions = new TreeMap<>();
- supportedVersions.put(11, LocalDate.of(2024, 9, 30)); // Temurin: 2024-10-31
supportedVersions.put(17, LocalDate.of(2026, 3, 31)); // Temurin: 2027-10-31
supportedVersions.put(21, LocalDate.of(2027, 9, 30)); // Temurin: 2029-09-30
SUPPORTED_JAVA_VERSIONS = Collections.unmodifiableNavigableMap(supportedVersions);
diff --git a/core/src/main/java/jenkins/org/apache/commons/validator/routines/UrlValidator.java b/core/src/main/java/jenkins/org/apache/commons/validator/routines/UrlValidator.java
index 40c0eae1d13f..555d0c58f5e3 100644
--- a/core/src/main/java/jenkins/org/apache/commons/validator/routines/UrlValidator.java
+++ b/core/src/main/java/jenkins/org/apache/commons/validator/routines/UrlValidator.java
@@ -420,7 +420,7 @@ protected boolean isValidAuthority(String authority) {
}
}
String port = authorityMatcher.group(PARSE_AUTHORITY_PORT);
- if (port != null && port.length() > 0) {
+ if (port != null && !port.isEmpty()) {
try {
int iPort = Integer.parseInt(port);
if (iPort < 0 || iPort > MAX_UNSIGNED_16_BIT_INT) {
@@ -433,7 +433,7 @@ protected boolean isValidAuthority(String authority) {
}
String extra = authorityMatcher.group(PARSE_AUTHORITY_EXTRA);
- if (extra != null && extra.trim().length() > 0) {
+ if (extra != null && !extra.trim().isEmpty()) {
return false;
}
diff --git a/core/src/main/java/jenkins/security/ApiTokenProperty.java b/core/src/main/java/jenkins/security/ApiTokenProperty.java
index 69904a0e4785..464fdcbdf16c 100644
--- a/core/src/main/java/jenkins/security/ApiTokenProperty.java
+++ b/core/src/main/java/jenkins/security/ApiTokenProperty.java
@@ -33,6 +33,7 @@
import hudson.model.User;
import hudson.model.UserProperty;
import hudson.model.UserPropertyDescriptor;
+import hudson.model.userproperty.UserPropertyCategory;
import hudson.security.ACL;
import hudson.util.HttpResponses;
import hudson.util.Secret;
@@ -657,6 +658,11 @@ public HttpResponse doRevokeAllExcept(@AncestorInPath User u,
return HttpResponses.ok();
}
+
+ @Override
+ public @NonNull UserPropertyCategory getUserPropertyCategory() {
+ return UserPropertyCategory.get(UserPropertyCategory.Security.class);
+ }
}
/**
diff --git a/core/src/main/java/jenkins/security/BasicHeaderProcessor.java b/core/src/main/java/jenkins/security/BasicHeaderProcessor.java
index 18ea7b5174c0..45b5eb0c70a2 100644
--- a/core/src/main/java/jenkins/security/BasicHeaderProcessor.java
+++ b/core/src/main/java/jenkins/security/BasicHeaderProcessor.java
@@ -9,6 +9,7 @@
import hudson.util.Scrambler;
import java.io.IOException;
import java.util.List;
+import java.util.Locale;
import java.util.logging.Logger;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
@@ -64,7 +65,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha
HttpServletResponse rsp = (HttpServletResponse) response;
String authorization = req.getHeader("Authorization");
- if (authorization != null && authorization.toLowerCase().startsWith("Basic ".toLowerCase())) {
+ if (authorization != null && authorization.toLowerCase(Locale.ROOT).startsWith("Basic ".toLowerCase(Locale.ROOT))) {
// authenticate the user
String uidpassword = Scrambler.descramble(authorization.substring(6));
int idx = uidpassword.indexOf(':');
diff --git a/core/src/main/java/jenkins/security/LastGrantedAuthoritiesProperty.java b/core/src/main/java/jenkins/security/LastGrantedAuthoritiesProperty.java
index 52f74d452ffa..cb8b8accf295 100644
--- a/core/src/main/java/jenkins/security/LastGrantedAuthoritiesProperty.java
+++ b/core/src/main/java/jenkins/security/LastGrantedAuthoritiesProperty.java
@@ -6,6 +6,7 @@
import hudson.model.User;
import hudson.model.UserProperty;
import hudson.model.UserPropertyDescriptor;
+import hudson.model.userproperty.UserPropertyCategory;
import hudson.security.SecurityRealm;
import java.io.IOException;
import java.util.ArrayList;
@@ -171,6 +172,11 @@ public boolean isEnabled() {
public UserProperty newInstance(User user) {
return null;
}
+
+ @Override
+ public @NonNull UserPropertyCategory getUserPropertyCategory() {
+ return UserPropertyCategory.get(UserPropertyCategory.Invisible.class);
+ }
}
private static final Logger LOGGER = Logger.getLogger(LastGrantedAuthoritiesProperty.class.getName());
diff --git a/core/src/main/java/jenkins/security/RedactSecretJsonInErrorMessageSanitizer.java b/core/src/main/java/jenkins/security/RedactSecretJsonInErrorMessageSanitizer.java
index 733a6c89a1f2..1e201bcd3dbc 100644
--- a/core/src/main/java/jenkins/security/RedactSecretJsonInErrorMessageSanitizer.java
+++ b/core/src/main/java/jenkins/security/RedactSecretJsonInErrorMessageSanitizer.java
@@ -88,7 +88,6 @@ private Object copyAndSanitize(Object value) {
}
}
- @SuppressWarnings("unchecked")
private JSONObject copyAndSanitizeObject(JSONObject jsonObject) {
Set redactedKeySet = retrieveRedactedKeys(jsonObject);
JSONObject result = new JSONObject();
diff --git a/core/src/main/java/jenkins/security/RekeySecretAdminMonitor.java b/core/src/main/java/jenkins/security/RekeySecretAdminMonitor.java
deleted file mode 100644
index c3b548004515..000000000000
--- a/core/src/main/java/jenkins/security/RekeySecretAdminMonitor.java
+++ /dev/null
@@ -1,160 +0,0 @@
-package jenkins.security;
-
-import hudson.Extension;
-import hudson.Functions;
-import hudson.Util;
-import hudson.init.InitMilestone;
-import hudson.init.Initializer;
-import hudson.model.TaskListener;
-import hudson.util.HttpResponses;
-import hudson.util.SecretRewriter;
-import hudson.util.VersionNumber;
-import java.io.File;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.security.GeneralSecurityException;
-import java.util.Date;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import jenkins.management.AsynchronousAdministrativeMonitor;
-import jenkins.model.Jenkins;
-import jenkins.util.io.FileBoolean;
-import org.jenkinsci.Symbol;
-import org.kohsuke.stapler.HttpResponse;
-import org.kohsuke.stapler.StaplerRequest;
-import org.kohsuke.stapler.interceptor.RequirePOST;
-
-/**
- * Warns the administrator to run {@link SecretRewriter}
- *
- * @author Kohsuke Kawaguchi
- */
-@Extension @Symbol("rekeySecret")
-public class RekeySecretAdminMonitor extends AsynchronousAdministrativeMonitor {
-
- /**
- * Whether we detected a need to run the rewrite program.
- * Once we set it to true, we'll never turn it off.
- *
- * If the admin decides to dismiss this warning, we use {@link #isEnabled()} for that.
- *
- * In this way we can correctly differentiate all the different states.
- */
- private final FileBoolean needed = state("needed");
-
- /**
- * If the scanning process has run to the completion, we set to this true.
- */
- private final FileBoolean done = state("done");
-
- /**
- * If the rewrite process is scheduled upon the next boot.
- */
- private final FileBoolean scanOnBoot = state("scanOnBoot");
-
- @SuppressWarnings("OverridableMethodCallInConstructor") // should have been final
- public RekeySecretAdminMonitor() throws IOException {
- // if JENKINS_HOME existed <1.497, we need to offer rewrite
- // this computation needs to be done and the value be captured,
- // since $JENKINS_HOME/config.xml can be saved later before the user has
- // actually rewritten XML files.
- Jenkins j = Jenkins.get();
- if (j.isUpgradedFromBefore(new VersionNumber("1.496.*"))
- && new FileBoolean(new File(j.getRootDir(), "secret.key.not-so-secret")).isOff())
- needed.on();
- Util.deleteRecursive(new File(getBaseDir(), "backups")); // SECURITY-376: no longer used
- }
-
- @Override
- public boolean isActivated() {
- return needed.isOn();
- }
-
- /**
- * Indicates that the re-keying has run to the completion.
- */
- public boolean isDone() {
- return done.isOn();
- }
-
- public void setNeeded() {
- needed.on();
- }
-
- public boolean isScanOnBoot() {
- return scanOnBoot.isOn();
- }
-
- @Override
- public boolean isSecurity() {
- return true;
- }
-
- @RequirePOST
- public HttpResponse doScan(StaplerRequest req) throws IOException, GeneralSecurityException {
- if (req.hasParameter("background")) {
- start(false);
- } else
- if (req.hasParameter("schedule")) {
- scanOnBoot.on();
- } else
- if (req.hasParameter("dismiss")) {
- disable(true);
- } else
- throw HttpResponses.error(400, "Invalid request submission: " + req.getParameterMap());
-
- return HttpResponses.redirectViaContextPath("/manage");
- }
-
-
- private FileBoolean state(String name) {
- return new FileBoolean(new File(getBaseDir(), name));
- }
-
- @Initializer(fatal = false, after = InitMilestone.PLUGINS_STARTED, before = InitMilestone.EXTENSIONS_AUGMENTED)
- // as early as possible, but this needs to be late enough that the ConfidentialStore is available
- public void scanOnReboot() throws InterruptedException, IOException, GeneralSecurityException {
- FileBoolean flag = scanOnBoot;
- if (flag.isOn()) {
- flag.off();
- start(false).join();
- // block the boot until the rewrite process is complete
- // don't let the failure in RekeyThread block Jenkins boot.
- }
- }
-
- @Override
- public String getDisplayName() {
- return Messages.RekeySecretAdminMonitor_DisplayName();
- }
-
- /**
- * Rewrite log file.
- */
- @Override
- protected File getLogFile() {
- return new File(getBaseDir(), "rekey.log");
- }
-
- @Override
- protected void fix(TaskListener listener) throws Exception {
- LOGGER.info("Initiating a re-keying of secrets. See " + getLogFile());
-
- SecretRewriter rewriter = new SecretRewriter();
-
- try {
- PrintStream log = listener.getLogger();
- log.println("Started re-keying " + new Date());
- int count = rewriter.rewriteRecursive(Jenkins.get().getRootDir(), listener);
- log.printf("Completed re-keying %d files on %s%n", count, new Date());
- new RekeySecretAdminMonitor().done.on();
- LOGGER.info("Secret re-keying completed");
- } catch (Exception e) {
- LOGGER.log(Level.SEVERE, "Fatal failure in re-keying secrets", e);
- Functions.printStackTrace(e, listener.error("Fatal failure in rewriting secrets"));
- }
- }
-
- private static final Logger LOGGER = Logger.getLogger(RekeySecretAdminMonitor.class.getName());
-
-}
diff --git a/core/src/main/java/jenkins/security/ResourceDomainConfiguration.java b/core/src/main/java/jenkins/security/ResourceDomainConfiguration.java
index 5c569ce40d50..cd552fc65130 100644
--- a/core/src/main/java/jenkins/security/ResourceDomainConfiguration.java
+++ b/core/src/main/java/jenkins/security/ResourceDomainConfiguration.java
@@ -150,8 +150,7 @@ private FormValidation checkUrl(String resourceRootUrlString, boolean allowOnlin
// Send a request to /instance-identity/ at the resource root URL and check whether it is this Jenkins
try {
URLConnection urlConnection = new URI(resourceRootUrlString + "instance-identity/").toURL().openConnection();
- if (urlConnection instanceof HttpURLConnection) {
- HttpURLConnection httpURLConnection = (HttpURLConnection) urlConnection;
+ if (urlConnection instanceof HttpURLConnection httpURLConnection) {
int responseCode = httpURLConnection.getResponseCode();
if (responseCode == 200) {
diff --git a/core/src/main/java/jenkins/security/s2m/JarURLValidatorImpl.java b/core/src/main/java/jenkins/security/s2m/JarURLValidatorImpl.java
new file mode 100644
index 000000000000..7fafcea946d5
--- /dev/null
+++ b/core/src/main/java/jenkins/security/s2m/JarURLValidatorImpl.java
@@ -0,0 +1,104 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2024 CloudBees, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package jenkins.security.s2m;
+
+import edu.umd.cs.findbugs.annotations.Nullable;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import hudson.Extension;
+import hudson.PluginManager;
+import hudson.remoting.Channel;
+import hudson.remoting.ChannelBuilder;
+import hudson.remoting.JarURLValidator;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import jenkins.model.Jenkins;
+import jenkins.security.ChannelConfigurator;
+import jenkins.util.SystemProperties;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
+
+@Restricted(NoExternalUse.class)
+@Deprecated
+@Extension
+public class JarURLValidatorImpl extends ChannelConfigurator implements JarURLValidator {
+
+ public static final Logger LOGGER = Logger.getLogger(JarURLValidatorImpl.class.getName());
+
+ @Override
+ public void onChannelBuilding(ChannelBuilder builder, @Nullable Object context) {
+ LOGGER.log(Level.CONFIG, () -> "Setting up JarURLValidatorImpl for context: " + context);
+ builder.withProperty(JarURLValidator.class, this);
+ }
+
+ @Override
+ public void validate(URL url) throws IOException {
+ final String rejectAllProp = JarURLValidatorImpl.class.getName() + ".REJECT_ALL";
+ if (SystemProperties.getBoolean(rejectAllProp)) {
+ LOGGER.log(Level.FINE, () -> "Rejecting URL due to configuration: " + url);
+ throw new IOException("The system property '" + rejectAllProp + "' has been set, so all attempts by agents to load jars from the controller are rejected."
+ + " Update the agent.jar of the affected agent to a version released in August 2024 or later to prevent this error."); // TODO better version spec
+ }
+ final String allowAllProp = Channel.class.getName() + ".DISABLE_JAR_URL_VALIDATOR";
+ if (SystemProperties.getBoolean(allowAllProp)) {
+ LOGGER.log(Level.FINE, () -> "Allowing URL due to configuration: " + url);
+ return;
+ }
+ if (!isAllowedJar(url)) {
+ LOGGER.log(Level.FINE, () -> "Rejecting URL: " + url);
+ throw new IOException("This URL does not point to a jar file allowed to be requested by agents: " + url + "."
+ + " Update the agent.jar of the affected agent to a version released in August 2024 or later to prevent this error."
+ + " Alternatively, set the system property '" + allowAllProp + "' to 'true' if all the code built by Jenkins is as trusted as an administrator.");
+ } else {
+ LOGGER.log(Level.FINE, () -> "Allowing URL: " + url);
+ }
+ }
+ @SuppressFBWarnings(
+ value = "DMI_COLLECTION_OF_URLS",
+ justification = "All URLs point to local files, so no DNS lookup.")
+ private static boolean isAllowedJar(URL url) {
+ final ClassLoader classLoader = Jenkins.get().getPluginManager().uberClassLoader;
+ if (classLoader instanceof PluginManager.UberClassLoader uberClassLoader) {
+ if (uberClassLoader.isPluginJar(url)) {
+ LOGGER.log(Level.FINER, () -> "Determined to be plugin jar: " + url);
+ return true;
+ }
+ }
+
+ final ClassLoader coreClassLoader = Jenkins.class.getClassLoader();
+ if (coreClassLoader instanceof URLClassLoader urlClassLoader) {
+ if (Set.of(urlClassLoader.getURLs()).contains(url)) {
+ LOGGER.log(Level.FINER, () -> "Determined to be core jar: " + url);
+ return true;
+ }
+ }
+
+ LOGGER.log(Level.FINER, () -> "Neither core nor plugin jar: " + url);
+ return false;
+ }
+}
diff --git a/core/src/main/java/jenkins/security/seed/UserSeedChangeListener.java b/core/src/main/java/jenkins/security/seed/UserSeedChangeListener.java
index 02753addb79d..e87506315a41 100644
--- a/core/src/main/java/jenkins/security/seed/UserSeedChangeListener.java
+++ b/core/src/main/java/jenkins/security/seed/UserSeedChangeListener.java
@@ -35,7 +35,7 @@
/**
* Listener notified when a user was requested to changed their seed
- * @since 2.160 and 2.150.2, but restricted (unavailable to plugins) before TODO
+ * @since 2.160 and 2.150.2, but restricted (unavailable to plugins) before 2.406
*/
public abstract class UserSeedChangeListener implements ExtensionPoint {
private static final Logger LOGGER = Logger.getLogger(SecurityListener.class.getName());
diff --git a/core/src/main/java/jenkins/security/seed/UserSeedProperty.java b/core/src/main/java/jenkins/security/seed/UserSeedProperty.java
index d7420910cb64..968ee9320f58 100644
--- a/core/src/main/java/jenkins/security/seed/UserSeedProperty.java
+++ b/core/src/main/java/jenkins/security/seed/UserSeedProperty.java
@@ -31,6 +31,7 @@
import hudson.model.User;
import hudson.model.UserProperty;
import hudson.model.UserPropertyDescriptor;
+import hudson.model.userproperty.UserPropertyCategory;
import hudson.util.HttpResponses;
import java.io.IOException;
import java.security.SecureRandom;
@@ -153,5 +154,10 @@ public synchronized HttpResponse doRenewSessionSeed(@AncestorInPath @NonNull Use
public boolean isEnabled() {
return !DISABLE_USER_SEED && !HIDE_USER_SEED_SECTION;
}
+
+ @Override
+ public @NonNull UserPropertyCategory getUserPropertyCategory() {
+ return UserPropertyCategory.get(UserPropertyCategory.Security.class);
+ }
}
}
diff --git a/core/src/main/java/jenkins/security/stapler/StaplerFilteredActionListener.java b/core/src/main/java/jenkins/security/stapler/StaplerFilteredActionListener.java
index 46b7cab9028b..cc658c275fcf 100644
--- a/core/src/main/java/jenkins/security/stapler/StaplerFilteredActionListener.java
+++ b/core/src/main/java/jenkins/security/stapler/StaplerFilteredActionListener.java
@@ -50,7 +50,7 @@ public class StaplerFilteredActionListener implements FilteredDoActionTriggerLis
@Override
public boolean onDoActionTrigger(Function f, StaplerRequest req, StaplerResponse rsp, Object node) {
- LOGGER.log(Level.WARNING, LOG_MESSAGE, new Object[]{
+ LOGGER.log(Level.FINER, LOG_MESSAGE, new Object[]{
req.getPathInfo(),
f.getSignature(),
});
@@ -59,7 +59,7 @@ public boolean onDoActionTrigger(Function f, StaplerRequest req, StaplerResponse
@Override
public boolean onGetterTrigger(Function f, StaplerRequest req, StaplerResponse rsp, Object node, String expression) {
- LOGGER.log(Level.WARNING, LOG_MESSAGE, new Object[]{
+ LOGGER.log(Level.FINER, LOG_MESSAGE, new Object[]{
req.getPathInfo(),
f.getSignature(),
});
@@ -68,7 +68,7 @@ public boolean onGetterTrigger(Function f, StaplerRequest req, StaplerResponse r
@Override
public boolean onFieldTrigger(FieldRef f, StaplerRequest req, StaplerResponse staplerResponse, Object node, String expression) {
- LOGGER.log(Level.WARNING, LOG_MESSAGE, new Object[]{
+ LOGGER.log(Level.FINER, LOG_MESSAGE, new Object[]{
req.getPathInfo(),
f.getSignature(),
});
@@ -77,7 +77,7 @@ public boolean onFieldTrigger(FieldRef f, StaplerRequest req, StaplerResponse st
@Override
public boolean onDispatchTrigger(StaplerRequest req, StaplerResponse rsp, Object node, String viewName) {
- LOGGER.warning(() -> "New Stapler dispatch rules result in the URL \"" + req.getPathInfo() + "\" no longer being allowed. " +
+ LOGGER.finer(() -> "New Stapler dispatch rules result in the URL \"" + req.getPathInfo() + "\" no longer being allowed. " +
"If you consider it safe to use, add the following to the whitelist: \"" + node.getClass().getName() + " " + viewName + "\". " +
"Learn more: https://www.jenkins.io/redirect/stapler-facet-restrictions");
return false;
diff --git a/core/src/main/java/jenkins/util/TreeString.java b/core/src/main/java/jenkins/util/TreeString.java
index 3bcdc95d90ca..58890d225893 100644
--- a/core/src/main/java/jenkins/util/TreeString.java
+++ b/core/src/main/java/jenkins/util/TreeString.java
@@ -64,7 +64,7 @@ public final class TreeString implements Serializable {
}
/* package */TreeString(final TreeString parent, final String label) {
- assert parent == null || label.length() > 0; // if there's a parent,
+ assert parent == null || !label.isEmpty(); // if there's a parent,
// label can't be empty.
this.parent = parent;
diff --git a/core/src/main/java/jenkins/util/URLClassLoader2.java b/core/src/main/java/jenkins/util/URLClassLoader2.java
index 72611bc49432..a6e5194e14f3 100644
--- a/core/src/main/java/jenkins/util/URLClassLoader2.java
+++ b/core/src/main/java/jenkins/util/URLClassLoader2.java
@@ -17,14 +17,43 @@ public class URLClassLoader2 extends URLClassLoader implements JenkinsClassLoade
registerAsParallelCapable();
}
+ /**
+ * @deprecated use {@link URLClassLoader2#URLClassLoader2(String, URL[])}
+ */
+ @Deprecated(since = "2.459")
public URLClassLoader2(URL[] urls) {
super(urls);
}
+ /**
+ * @deprecated use {@link URLClassLoader2#URLClassLoader2(String, URL[], ClassLoader)}
+ */
+ @Deprecated(since = "2.459")
public URLClassLoader2(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
+ /**
+ * Create a new {@link URLClassLoader2} with the given name and URLS and the {@link #getSystemClassLoader()} as its parent.
+ * @param name name of this classloader.
+ * @param urls the list of URLS to find classes in.
+ * @since 2.459
+ */
+ public URLClassLoader2(String name, URL[] urls) {
+ super(name, urls, getSystemClassLoader());
+ }
+
+ /**
+ * Create a new {@link URLClassLoader2} with the given name, URLS parent.
+ * @param name name of this classloader.
+ * @param urls the list of URLS to find classes in.
+ * @param parent the parent to search for classes before we look in the {@code urls}
+ * @since 2.459
+ */
+ public URLClassLoader2(String name, URL[] urls, ClassLoader parent) {
+ super(name, urls, parent);
+ }
+
@Override
public void addURL(URL url) {
super.addURL(url);
diff --git a/core/src/main/java/jenkins/util/VirtualFile.java b/core/src/main/java/jenkins/util/VirtualFile.java
index bf9fc49bd48d..6c754a50bbc4 100644
--- a/core/src/main/java/jenkins/util/VirtualFile.java
+++ b/core/src/main/java/jenkins/util/VirtualFile.java
@@ -46,6 +46,7 @@
import java.io.Serializable;
import java.net.URI;
import java.net.URL;
+import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.OpenOption;
@@ -375,7 +376,7 @@ public int zip(OutputStream outputStream, String includes, String excludes, bool
Collection files = list(includes, excludes, useDefaultExcludes, openOptions);
try (ZipOutputStream zos = new ZipOutputStream(outputStream)) {
- zos.setEncoding(System.getProperty("file.encoding")); // TODO JENKINS-20663 make this overridable via query parameter
+ zos.setEncoding(Charset.defaultCharset().displayName()); // TODO JENKINS-20663 make this overridable via query parameter
for (String relativePath : files) {
VirtualFile virtualFile = this.child(relativePath);
diff --git a/core/src/main/java/jenkins/widgets/BuildTimeTrend.java b/core/src/main/java/jenkins/widgets/BuildTimeTrend.java
index 59ab879f05da..dd0f0434ecb3 100644
--- a/core/src/main/java/jenkins/widgets/BuildTimeTrend.java
+++ b/core/src/main/java/jenkins/widgets/BuildTimeTrend.java
@@ -25,7 +25,9 @@
package jenkins.widgets;
import hudson.model.AbstractBuild;
+import hudson.model.AbstractProject;
import hudson.model.BallColor;
+import hudson.model.Job;
import hudson.model.Node;
import hudson.model.Run;
import jenkins.console.ConsoleUrlProvider;
@@ -37,6 +39,10 @@
@Restricted(DoNotUse.class) // only for buildTimeTrend.jelly
public class BuildTimeTrend extends RunListProgressiveRendering {
+ public boolean isAbstractProject(Job, ?> job) {
+ return job instanceof AbstractProject;
+ }
+
@Override protected void calculate(Run, ?> build, JSONObject element) {
BallColor iconColor = build.getIconColor();
element.put("iconName", iconColor.getIconName());
@@ -46,6 +52,8 @@ public class BuildTimeTrend extends RunListProgressiveRendering {
element.put("displayName", build.getDisplayName());
element.put("duration", build.getDuration());
element.put("durationString", build.getDurationString());
+ element.put("timestampString", build.getTimestampString());
+ element.put("timestampString2", build.getTimestampString2());
element.put("consoleUrl", ConsoleUrlProvider.getRedirectUrl(build));
if (build instanceof AbstractBuild) {
AbstractBuild, ?> b = (AbstractBuild) build;
diff --git a/core/src/main/java/jenkins/widgets/HistoryPageEntry.java b/core/src/main/java/jenkins/widgets/HistoryPageEntry.java
index ac5e0a772692..98f0a7aac58e 100644
--- a/core/src/main/java/jenkins/widgets/HistoryPageEntry.java
+++ b/core/src/main/java/jenkins/widgets/HistoryPageEntry.java
@@ -57,8 +57,7 @@ public long getEntryId() {
protected static long getEntryId(@NonNull Object entry) {
if (entry instanceof QueueItem) {
return ((QueueItem) entry).getId();
- } else if (entry instanceof Run) {
- Run run = (Run) entry;
+ } else if (entry instanceof Run run) {
return Long.MIN_VALUE + run.getNumber();
} else if (entry instanceof Number) {
// Used for testing purposes because of JENKINS-30899 and JENKINS-30909
diff --git a/core/src/main/java/org/jenkins/ui/icon/Icon.java b/core/src/main/java/org/jenkins/ui/icon/Icon.java
index e99822dac653..95524c8c4b74 100644
--- a/core/src/main/java/org/jenkins/ui/icon/Icon.java
+++ b/core/src/main/java/org/jenkins/ui/icon/Icon.java
@@ -279,7 +279,7 @@ public static String toNormalizedCSSSelector(String classNames) {
// Trim all tokens first
for (String classNameTok : classNameTokA) {
String trimmedToken = classNameTok.trim();
- if (trimmedToken.length() > 0) {
+ if (!trimmedToken.isEmpty()) {
classNameTokL.add(trimmedToken);
}
}
diff --git a/core/src/main/java/org/jenkins/ui/symbol/Symbol.java b/core/src/main/java/org/jenkins/ui/symbol/Symbol.java
index 9973acc18da5..52c7faee0249 100644
--- a/core/src/main/java/org/jenkins/ui/symbol/Symbol.java
+++ b/core/src/main/java/org/jenkins/ui/symbol/Symbol.java
@@ -8,13 +8,31 @@
import hudson.Util;
import java.io.IOException;
import java.io.InputStream;
+import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
import jenkins.model.Jenkins;
import org.apache.commons.io.IOUtils;
+import org.apache.tools.ant.filters.StringInputStream;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
/**
* Helper class to load symbols from Jenkins core or plugins.
@@ -55,13 +73,13 @@ public static String get(@NonNull SymbolRequest request) {
.computeIfAbsent(identifier, key -> new ConcurrentHashMap<>())
.computeIfAbsent(name, key -> loadSymbol(identifier, key));
if ((tooltip != null && !tooltip.isBlank()) && (htmlTooltip == null || htmlTooltip.isBlank())) {
- symbol = symbol.replaceAll("