diff --git a/.github/dependabot.yml b/.github/dependabot.yml index a44d07b50c54..8c2bd4291a6e 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -58,7 +58,7 @@ updates: versions: [">=7.0.0"] - package-ecosystem: "maven" directory: "/" - target-branch: "stable-2.440" + target-branch: "stable-2.452" labels: - "into-lts" - "needs-justification" diff --git a/.github/workflows/label-conflicting-pr.yml b/.github/workflows/label-conflicting-pr.yml index 80079296c113..ae90f55bc77d 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.1 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..31854bf8266f 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@69320dbe05506a9a39fc8ae11030b214ec2d1f87 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@69320dbe05506a9a39fc8ae11030b214ec2d1f87 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@69320dbe05506a9a39fc8ae11030b214ec2d1f87 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@69320dbe05506a9a39fc8ae11030b214ec2d1f87 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@69320dbe05506a9a39fc8ae11030b214ec2d1f87 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: diff --git a/.gitpod/Dockerfile b/.gitpod/Dockerfile index 6b71c6707163..f2475612b107 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.7 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/.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.incrementals git-changelist-maven-extension - 1.7 + 1.8 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..3ab8d159b66e 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=5858.v4b_c4e3b_16099 if [[ $# -eq 0 ]]; then export JDK=17 diff --git a/bom/pom.xml b/bom/pom.xml index 45a4db8f223a..d3aed901a32b 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -38,9 +38,9 @@ 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 + 2.0.13 + 1869.v3605a_771fb_e3 2.4.21 @@ -56,7 +56,7 @@ THE SOFTWARE. org.springframework spring-framework-bom - 5.3.33 + 5.3.36 pom import @@ -64,7 +64,7 @@ THE SOFTWARE. org.springframework.security spring-security-bom - 5.8.11 + 5.8.12 pom import @@ -82,7 +82,7 @@ THE SOFTWARE. com.google.guava guava - 33.1.0-jre + 33.2.0-jre @@ -113,22 +113,17 @@ THE SOFTWARE. commons-codec commons-codec - 1.16.1 + 1.17.0 commons-collections commons-collections 3.2.2 - - commons-fileupload - commons-fileupload - 1.5 - commons-io commons-io - 2.16.0 + 2.16.1 commons-jelly @@ -150,6 +145,11 @@ THE SOFTWARE. jenkins-stapler-support 1.1 + + jakarta.servlet + jakarta.servlet-api + 4.0.4 + jakarta.servlet.jsp.jstl jakarta.servlet.jsp.jstl-api @@ -190,6 +190,41 @@ THE SOFTWARE. commons-compress 1.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.groovy groovy-all @@ -254,7 +289,7 @@ THE SOFTWARE. org.jvnet.hudson commons-jelly-tags-define - 1.1-jenkins-20230713 + 1.1-jenkins-20240510 org.jvnet.localizer @@ -294,7 +329,7 @@ THE SOFTWARE. org.kohsuke.stapler json-lib - 2.4-jenkins-3 + 2.4-jenkins-7 org.kohsuke.stapler @@ -311,31 +346,6 @@ 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.jcifs jcifs @@ -366,7 +376,7 @@ THE SOFTWARE. commons-logging commons-logging - 1.3.1 + 1.3.2 provided diff --git a/cli/pom.xml b/cli/pom.xml index 342b4061016d..14d6323ca762 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -119,7 +119,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.5.2 + 3.5.3 diff --git a/core/pom.xml b/core/pom.xml index 3d1f16532c01..61c48e489794 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -39,7 +39,7 @@ THE SOFTWARE. https://github.com/jenkinsci/jenkins - 2.9.1 + 2.10.0 @@ -160,10 +160,6 @@ THE SOFTWARE. commons-collections commons-collections - - commons-fileupload - commons-fileupload - commons-io commons-io @@ -284,6 +280,14 @@ THE SOFTWARE. + + org.apache.commons + commons-fileupload2-core + + + org.apache.commons + commons-fileupload2-javax + org.codehaus.groovy groovy-all @@ -409,26 +413,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.slf4j jcl-over-slf4j @@ -459,7 +443,6 @@ THE SOFTWARE. jakarta.servlet jakarta.servlet-api - 4.0.4 provided diff --git a/core/src/main/java/hudson/ClassicPluginStrategy.java b/core/src/main/java/hudson/ClassicPluginStrategy.java index 10b0d179fdd7..d9b37f0fac29 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) { @@ -263,15 +268,28 @@ public static List getImpliedDependencies(String plugi return DetachedPluginsUtil.getImpliedDependencies(pluginName, jenkinsVersion); } - @Deprecated + /** + * @deprecated since TODO use {@link #createClassLoader(String, List, ClassLoader, Attributes)} + */ + @Deprecated(since = "TODO") 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 TODO use {@link #createClassLoader(String, List, ClassLoader, Attributes)} */ + @Deprecated(since="TODO") 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 TODO + */ + 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/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..b0cf56bf907d 100644 --- a/core/src/main/java/hudson/FilePath.java +++ b/core/src/main/java/hudson/FilePath.java @@ -80,6 +80,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; @@ -132,7 +133,7 @@ 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; @@ -1166,7 +1167,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 +1183,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}. 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..5d80c6e623e4 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; @@ -1098,7 +1101,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 +1256,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 +1445,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 +1834,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 +1883,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 +2347,7 @@ public static final class UberClassLoader extends ClassLoader { } public UberClassLoader(List activePlugins) { - super(PluginManager.class.getClassLoader()); + super("UberClassLoader", PluginManager.class.getClassLoader()); this.activePlugins = activePlugins; } 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 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/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/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/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/Job.java b/core/src/main/java/hudson/model/Job.java index 8c4e3183100a..92f1d374fb08 100644 --- a/core/src/main/java/hudson/model/Job.java +++ b/core/src/main/java/hudson/model/Job.java @@ -626,11 +626,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<>() { diff --git a/core/src/main/java/hudson/model/Label.java b/core/src/main/java/hudson/model/Label.java index b0c32f212a09..94d8b9683ed7 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 */ diff --git a/core/src/main/java/hudson/model/View.java b/core/src/main/java/hudson/model/View.java index 0df916a19b5a..e3775143730a 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; @@ -801,11 +803,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)); + } } } } 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/queue/MappingWorksheet.java b/core/src/main/java/hudson/model/queue/MappingWorksheet.java index 54829ec0dac2..9dbeaa3816f4 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; } 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/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/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/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/jenkins/agents/CloudSet.java b/core/src/main/java/jenkins/agents/CloudSet.java index e69a3f9add3c..addefceb83ff 100644 --- a/core/src/main/java/jenkins/agents/CloudSet.java +++ b/core/src/main/java/jenkins/agents/CloudSet.java @@ -242,9 +242,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."); } diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java index 0808d2e7ce7f..c7453fffb0f9 100644 --- a/core/src/main/java/jenkins/model/Jenkins.java +++ b/core/src/main/java/jenkins/model/Jenkins.java @@ -2897,13 +2897,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 +2915,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); } @@ -4480,7 +4492,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/lazy/LazyBuildMixIn.java b/core/src/main/java/jenkins/model/lazy/LazyBuildMixIn.java index 570048c5327f..203ea976bccd 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 = "TODO") 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/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/util/URLClassLoader2.java b/core/src/main/java/jenkins/util/URLClassLoader2.java index 72611bc49432..3fdedcbeada0 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 = "TODO") public URLClassLoader2(URL[] urls) { super(urls); } + /** + * @deprecated use {@link URLClassLoader2#URLClassLoader2(String, URL[], ClassLoader)} + */ + @Deprecated(since = "TODO") 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 TODO + */ + 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 TODO + */ + 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/org/jenkins/ui/symbol/Symbol.java b/core/src/main/java/org/jenkins/ui/symbol/Symbol.java index 9973acc18da5..9a07556d7c71 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,30 @@ 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 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. @@ -88,12 +105,37 @@ private static String loadSymbol(String namespace, String name) { LOGGER.log(Level.FINE, "Failed to load symbol " + name, e); } } - return markup.replaceAll("().*?()", "$1$2") - .replaceAll(" "The given src for the svg is not a valid xml document"); + return PLACEHOLDER_SVG; + } + + return markup; } @CheckForNull diff --git a/core/src/main/resources/hudson/PluginManager/PluginCycleDependenciesMonitor/message.jelly b/core/src/main/resources/hudson/PluginManager/PluginCycleDependenciesMonitor/message.jelly index 5ccac430ecd2..cb20ebf8dfe7 100644 --- a/core/src/main/resources/hudson/PluginManager/PluginCycleDependenciesMonitor/message.jelly +++ b/core/src/main/resources/hudson/PluginManager/PluginCycleDependenciesMonitor/message.jelly @@ -24,7 +24,7 @@ THE SOFTWARE. -

+
${%PluginCycles}
diff --git a/core/src/main/resources/hudson/PluginManager/PluginDeprecationMonitor/message.jelly b/core/src/main/resources/hudson/PluginManager/PluginDeprecationMonitor/message.jelly index 45c4cead0a44..1f5ec400bd3c 100644 --- a/core/src/main/resources/hudson/PluginManager/PluginDeprecationMonitor/message.jelly +++ b/core/src/main/resources/hudson/PluginManager/PluginDeprecationMonitor/message.jelly @@ -24,7 +24,7 @@ THE SOFTWARE. -
+
${%DeprecatedPlugins}
diff --git a/core/src/main/resources/hudson/PluginManager/PluginUpdateMonitor/message.jelly b/core/src/main/resources/hudson/PluginManager/PluginUpdateMonitor/message.jelly index 73182ec483e4..b1f9ba193d49 100644 --- a/core/src/main/resources/hudson/PluginManager/PluginUpdateMonitor/message.jelly +++ b/core/src/main/resources/hudson/PluginManager/PluginUpdateMonitor/message.jelly @@ -24,7 +24,7 @@ THE SOFTWARE. -
+
${%RequiredPluginUpdates}
diff --git a/core/src/main/resources/hudson/PluginManager/advanced.jelly b/core/src/main/resources/hudson/PluginManager/advanced.jelly index 8d0e6abcd22e..81948afa6e80 100644 --- a/core/src/main/resources/hudson/PluginManager/advanced.jelly +++ b/core/src/main/resources/hudson/PluginManager/advanced.jelly @@ -37,7 +37,7 @@ THE SOFTWARE. -
+
${%proxyMovedBlurb(rootURL+"/manage/configure")}
diff --git a/core/src/main/resources/hudson/PluginManager/installed.jelly b/core/src/main/resources/hudson/PluginManager/installed.jelly index 8013742cf738..3c3ee43e0193 100644 --- a/core/src/main/resources/hudson/PluginManager/installed.jelly +++ b/core/src/main/resources/hudson/PluginManager/installed.jelly @@ -54,7 +54,7 @@ THE SOFTWARE. data-is-restart-required="${app.updateCenter.isRestartRequiredForCompletion()}" /> -
${%Warning}: ${%requires.restart}
+
${%Warning}: ${%requires.restart}