From 93451e20c20cfd84badeb0f37c38d4c0c7a5dad3 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Mon, 25 Oct 2021 12:51:44 +0000 Subject: [PATCH 01/44] [SECURITY-2423] --- .../main/resources/jenkins/security/s2m/filepath-filter.conf | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/main/resources/jenkins/security/s2m/filepath-filter.conf b/core/src/main/resources/jenkins/security/s2m/filepath-filter.conf index 4a666570a74b..e3a63abf1b28 100644 --- a/core/src/main/resources/jenkins/security/s2m/filepath-filter.conf +++ b/core/src/main/resources/jenkins/security/s2m/filepath-filter.conf @@ -26,6 +26,8 @@ deny all /build.xml # Similarly for Pipeline build (WorkflowRun) metadata: deny all /program.dat deny all /workflow($|/.*) +deny all /libs($|/.*) +deny all /checkpoints($|/.*) # Various plugins read/write files under build directories, so allow them all. # - git 1.x writes changelog.xml from the agent (2.x writes from the master so need not be listed) From cf388d2a04e6016d23eb93fa3cc804f2554b98f0 Mon Sep 17 00:00:00 2001 From: Daniel Beck Date: Mon, 25 Oct 2021 12:51:54 +0000 Subject: [PATCH 02/44] [SECURITY-2428] --- .../s2m/RunningBuildFilePathFilter.java | 152 ++++++++++++++++++ .../s2m/RunningBuildFilePathFilterTest.java | 146 +++++++++++++++++ 2 files changed, 298 insertions(+) create mode 100644 core/src/main/java/jenkins/security/s2m/RunningBuildFilePathFilter.java create mode 100644 test/src/test/java/jenkins/security/s2m/RunningBuildFilePathFilterTest.java diff --git a/core/src/main/java/jenkins/security/s2m/RunningBuildFilePathFilter.java b/core/src/main/java/jenkins/security/s2m/RunningBuildFilePathFilter.java new file mode 100644 index 000000000000..3e842a429ba4 --- /dev/null +++ b/core/src/main/java/jenkins/security/s2m/RunningBuildFilePathFilter.java @@ -0,0 +1,152 @@ +/* + * The MIT License + * + * Copyright 2021 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.CheckForNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import hudson.Extension; +import hudson.model.Computer; +import hudson.model.Executor; +import hudson.model.Queue; +import hudson.model.Run; +import hudson.remoting.ChannelBuilder; +import jenkins.ReflectiveFilePathFilter; +import jenkins.model.Jenkins; +import jenkins.security.ChannelConfigurator; +import jenkins.util.SystemProperties; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Pattern; + +/** + * When an agent tries to access build directories on the controller, limit it to those for builds running on that agent. + * + * @since TODO + */ +@Restricted(NoExternalUse.class) +public class RunningBuildFilePathFilter extends ReflectiveFilePathFilter { + + /** + * By default, unauthorized accesses will result in a {@link SecurityException}. + * If this is set to {@code false}, instead just log a warning. + */ + private static final String FAIL_PROPERTY = RunningBuildFilePathFilter.class.getName() + ".FAIL"; + + /** + * Disables this filter entirely. + */ + private static final String SKIP_PROPERTY = RunningBuildFilePathFilter.class.getName() + ".SKIP"; + + private static final Logger LOGGER = Logger.getLogger(RunningBuildFilePathFilter.class.getName()); + + private final Object context; + + public RunningBuildFilePathFilter(Object context) { + this.context = context; + } + + @Override + protected boolean op(String name, File path) throws SecurityException { + if (SystemProperties.getBoolean(SKIP_PROPERTY)) { + LOGGER.log(Level.FINE, () -> "Skipping check for '" + name + "' on '" + path + "'"); + return false; + } + if (!(context instanceof Computer)) { + LOGGER.log(Level.FINE, "No context provided for path access: " + path); + return false; + } + Computer c = (Computer) context; + + final Jenkins jenkins = Jenkins.get(); + + String patternString; + try { + patternString = Jenkins.expandVariablesForDirectory(jenkins.getRawBuildsDir(), "(.+)", "\\Q" + Jenkins.get().getRootDir().getCanonicalPath().replace('\\', '/') + "\\E/jobs/(.+)") + "/[0-9]+(/.*)?"; + } catch (IOException e) { + LOGGER.log(Level.WARNING, "Failed to obtain canonical path to Jenkins home directory", e); + throw new SecurityException("Failed to obtain canonical path"); // Minimal details + } + final Pattern pattern = Pattern.compile(patternString); + + String absolutePath; + try { + absolutePath = path.getCanonicalPath().replace('\\', '/'); + } catch (IOException e) { + LOGGER.log(Level.WARNING, "Failed to obtain canonical path to '" + path + "'", e); + throw new SecurityException("Failed to obtain canonical path"); // Minimal details + } + if (!pattern.matcher(absolutePath).matches()) { + /* This is not a build directory, so another filter will take care of it */ + LOGGER.log(Level.FINE, "Not a build directory, so skipping: " + absolutePath); + return false; + } + + final Path thePath = path.getAbsoluteFile().toPath(); + for (Executor executor : c.getExecutors()) { + Run build = findRun(executor.getCurrentExecutable()); + if (build == null) { + continue; + } + final Path buildDir = build.getRootDir().getAbsoluteFile().toPath(); + // If the directory being accessed is for a build currently running on this node, allow it + if (thePath.startsWith(buildDir)) { + return false; + } + } + + final String computerName = c.getName(); + if (SystemProperties.getBoolean(FAIL_PROPERTY, true)) { + // This filter can only prohibit by throwing a SecurityException; it never allows on its own. + LOGGER.log(Level.WARNING, "Rejecting unexpected agent-to-controller file path access: Agent '" + computerName + "' is attempting to access '" + absolutePath + "' using operation '" + name + "'. Learn more: https://www.jenkins.io/redirect/security-144/"); + throw new SecurityException("Agent tried to access build directory of a build not currently running on this system. Learn more: https://www.jenkins.io/redirect/security-144/"); + } else { + LOGGER.log(Level.WARNING, "Unexpected agent-to-controller file path access: Agent '" + computerName + "' is accessing '" + absolutePath + "' using operation '" + name + "'. Learn more: https://www.jenkins.io/redirect/security-144/"); + return false; + } + } + + private static @CheckForNull Run findRun(@CheckForNull Queue.Executable exec) { + if (exec == null) { + return null; + } else if (exec instanceof Run) { + return (Run) exec; + } else { + return findRun(exec.getParentExecutable()); + } + } + + @Extension + public static class ChannelConfiguratorImpl extends ChannelConfigurator { + @Override + public void onChannelBuilding(ChannelBuilder builder, @Nullable Object context) { + new RunningBuildFilePathFilter(context).installTo(builder, 150.0); + } + } +} diff --git a/test/src/test/java/jenkins/security/s2m/RunningBuildFilePathFilterTest.java b/test/src/test/java/jenkins/security/s2m/RunningBuildFilePathFilterTest.java new file mode 100644 index 000000000000..b560f4df312b --- /dev/null +++ b/test/src/test/java/jenkins/security/s2m/RunningBuildFilePathFilterTest.java @@ -0,0 +1,146 @@ +/* + * The MIT License + * + * Copyright 2021 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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import hudson.ExtensionList; +import hudson.FilePath; +import hudson.Functions; +import hudson.Launcher; +import hudson.model.AbstractBuild; +import hudson.model.BuildListener; +import hudson.model.FreeStyleProject; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.function.Function; +import jenkins.security.MasterToSlaveCallable; +import org.apache.commons.io.FileUtils; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.BuildWatcher; +import org.jvnet.hudson.test.Issue; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.TestBuilder; + +@Issue("SECURITY-2428") +public class RunningBuildFilePathFilterTest { + + @ClassRule + public static BuildWatcher buildWatcher = new BuildWatcher(); + + @Rule + public JenkinsRule r = new JenkinsRule(); + + @Test + public void accessPermittedOnlyFromCurrentBuild() throws Exception { + ExtensionList.lookupSingleton(AdminWhitelistRule.class).setMasterKillSwitch(false); + FreeStyleProject main = r.createFreeStyleProject("main"); + main.setAssignedNode(r.createSlave()); + WriteBackPublisher wbp = new WriteBackPublisher(); + main.getBuildersList().add(wbp); + // Normal case: writing to our own build directory + wbp.controllerFile = build -> new File(build.getRootDir(), "stuff.txt"); + r.buildAndAssertSuccess(main); + // Attacks: + wbp.legal = false; + // Writing to someone else’s build directory (covered by RunningBuildFilePathFilter) + FreeStyleProject other = r.createFreeStyleProject("other"); + r.buildAndAssertSuccess(other); + wbp.controllerFile = build -> new File(other.getBuildByNumber(1).getRootDir(), "hack"); + r.buildAndAssertSuccess(main); + // Writing to some other directory (covered by AdminWhitelistRule) + wbp.controllerFile = build -> new File(r.jenkins.getRootDir(), "hack"); + r.buildAndAssertSuccess(main); + // Writing to a sensitive file even in my own build dir (covered by AdminWhitelistRule) + wbp.controllerFile = build -> new File(build.getRootDir(), "build.xml"); + r.buildAndAssertSuccess(main); + // Writing to the directory of an earlier build + wbp.controllerFile = build -> new File(main.getBuildByNumber(1).getRootDir(), "stuff.txt"); + r.buildAndAssertSuccess(main); + + System.setProperty(RunningBuildFilePathFilter.class.getName() + ".FAIL", "false"); + try { + wbp.legal = true; + wbp.controllerFile = build -> new File(main.getBuildByNumber(1).getRootDir(), "stuff.txt"); + r.buildAndAssertSuccess(main); + } finally { + System.clearProperty(RunningBuildFilePathFilter.class.getName() + ".FAIL"); + } + } + + private static final class WriteBackPublisher extends TestBuilder { + Function, File> controllerFile; + boolean legal = true; + @Override + public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { + File f = controllerFile.apply(build); + listener.getLogger().println("Will try to write to " + f + "; legal? " + legal); + String text = build.getExternalizableId(); + try { + launcher.getChannel().call(new WriteBackCallable(new FilePath(f), text)); + if (legal) { + assertEquals(text, FileUtils.readFileToString(f, StandardCharsets.UTF_8)); + listener.getLogger().println("Allowed as expected"); + } else { + fail("should not have been allowed"); + } + } catch (Exception x) { + if (!legal && x.toString().contains("SecurityException")) { + // TODO assert error message is either from RunningBuildFilePathFilter or from SoloFilePathFilter + Functions.printStackTrace(x, listener.error("Rejected as expected!")); + } else { + throw x; + } + } + return true; + } + } + + private static final class WriteBackCallable extends MasterToSlaveCallable { + private final FilePath controllerFile; + private final String text; + WriteBackCallable(FilePath controllerFile, String text) { + this.controllerFile = controllerFile; + this.text = text; + } + @Override + public Void call() throws IOException { + assertTrue(controllerFile.isRemote()); + try { + controllerFile.write(text, null); + } catch (InterruptedException x) { + throw new IOException(x); + } + return null; + } + + } + +} From fa9ea93b610359a9efe4c61b743f00360d1462cb Mon Sep 17 00:00:00 2001 From: Daniel Beck Date: Mon, 25 Oct 2021 12:52:04 +0000 Subject: [PATCH 03/44] [SECURITY-2510] --- .../security/s2m/MasterKillSwitchConfiguration.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/jenkins/security/s2m/MasterKillSwitchConfiguration.java b/core/src/main/java/jenkins/security/s2m/MasterKillSwitchConfiguration.java index d0fe3d2372c8..bd4d656866fe 100644 --- a/core/src/main/java/jenkins/security/s2m/MasterKillSwitchConfiguration.java +++ b/core/src/main/java/jenkins/security/s2m/MasterKillSwitchConfiguration.java @@ -54,10 +54,17 @@ public boolean configure(StaplerRequest req, JSONObject json) throws FormExcepti } /** - * Returns true if the configuration of this subsystem becomes relevant. - * Unless this option is relevant, we don't let users choose this. + * Returns true if the configuration of this subsystem is relevant. + * + *

Historically, this was only shown when "security" (authn/authz) was enabled. + * That missed the use case of trusted local networks and Jenkins building public (untrusted) pull requests. + * To be sure we're not missing another case where this option is useful, just show it always.

*/ public boolean isRelevant() { - return jenkins.hasPermission(Jenkins.ADMINISTER) && jenkins.isUseSecurity(); + /* + * TODO Consider restricting this again to something like: + * return !jenkins.clouds.isEmpty() || !jenkins.getNodes().isEmpty(); + */ + return true; } } From 87a500add073b0ca2f4d9ce3f2ea1be3a21ce721 Mon Sep 17 00:00:00 2001 From: Daniel Beck Date: Mon, 25 Oct 2021 12:52:13 +0000 Subject: [PATCH 04/44] [SECURITY-2458] --- .../s2m/CallableDirectionChecker.java | 8 ++ pom.xml | 2 +- .../jenkins/security/Security2458Test.java | 116 ++++++++++++++++++ 3 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 test/src/test/java/jenkins/security/Security2458Test.java diff --git a/core/src/main/java/jenkins/security/s2m/CallableDirectionChecker.java b/core/src/main/java/jenkins/security/s2m/CallableDirectionChecker.java index a3206e7fc859..3c883dbd71c9 100644 --- a/core/src/main/java/jenkins/security/s2m/CallableDirectionChecker.java +++ b/core/src/main/java/jenkins/security/s2m/CallableDirectionChecker.java @@ -33,6 +33,8 @@ public class CallableDirectionChecker extends RoleChecker { private static final String BYPASS_PROP = CallableDirectionChecker.class.getName()+".allow"; + private static final String ALLOW_ANY_ROLE_PROP = CallableDirectionChecker.class.getName()+".allowAnyRole"; + /** * Switch to disable all the defense mechanism completely. * @@ -55,6 +57,12 @@ public void check(RoleSensitive subject, @NonNull Collection expected) thr return; // known to be safe } + if (expected.isEmpty() && SystemProperties.getBoolean(ALLOW_ANY_ROLE_PROP, true)) { + // TODO Is this even something we want to support, or should all infrastructure callables be exempted from the required role check? + LOGGER.log(Level.FINE, "Executing {0} is allowed since it is targeted for any role", name); + return; + } + if (isWhitelisted(subject,expected)) { // this subject is dubious, but we are letting it through as per whitelisting LOGGER.log(Level.FINE, "Explicitly allowing {0} to be sent from agent to controller", name); diff --git a/pom.xml b/pom.xml index 4336e05f5b60..fa5d51c09fad 100644 --- a/pom.xml +++ b/pom.xml @@ -91,7 +91,7 @@ THE SOFTWARE. https://www.jenkins.io/changelog - 4.11 + 4.11.1 3.14 diff --git a/test/src/test/java/jenkins/security/Security2458Test.java b/test/src/test/java/jenkins/security/Security2458Test.java new file mode 100644 index 000000000000..795742aa1c31 --- /dev/null +++ b/test/src/test/java/jenkins/security/Security2458Test.java @@ -0,0 +1,116 @@ +package jenkins.security; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import hudson.ExtensionList; +import hudson.remoting.Callable; +import java.io.IOException; +import java.util.Objects; +import jenkins.agents.AgentComputerUtil; +import jenkins.security.s2m.AdminWhitelistRule; +import jenkins.security.s2m.CallableDirectionChecker; +import org.jenkinsci.remoting.RoleChecker; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.function.ThrowingRunnable; +import org.jvnet.hudson.test.JenkinsRule; + +public class Security2458Test { + @Rule + public JenkinsRule r = new JenkinsRule(); + + @Before + public void enableAgentToControllerProtections() { + AdminWhitelistRule rule = ExtensionList.lookupSingleton(AdminWhitelistRule.class); + rule.setMasterKillSwitch(false); + } + + @Test + public void rejectBadCallable() throws Throwable { + // If the role check is empty, fail + assertThrowsIOExceptionCausedBySecurityException(() -> Objects.requireNonNull(r.createOnlineSlave().getChannel()).call(new CallableCaller(new BadCallable()))); + + // If it performs a no-op check, pass. Never do this in your plugin. + Objects.requireNonNull(r.createOnlineSlave().getChannel()).call(new CallableCaller(new EvilCallable())); + + // Explicit role check. + Objects.requireNonNull(r.createOnlineSlave().getChannel()).call(new CallableCaller(new GoodCallable())); + + // No-op role checks can be disallowed + System.setProperty(CallableDirectionChecker.class.getName() + ".allowAnyRole", "false"); + try { + assertThrowsIOExceptionCausedBySecurityException(() -> Objects.requireNonNull(r.createOnlineSlave().getChannel()).call(new CallableCaller(new EvilCallable()))); + } finally { + System.clearProperty(CallableDirectionChecker.class.getName() + ".allowAnyRole"); + } + } + + private static class CallableCaller extends MasterToSlaveCallable { + private final Callable callable; + + CallableCaller(Callable callable) { + this.callable = callable; + } + + @Override + public Object call() throws Throwable { + Objects.requireNonNull(AgentComputerUtil.getChannelToMaster()).call(callable); + return null; + } + } + + private static class BadCallable implements Callable { + @Override + public Object call() throws Exception { + return null; + } + @Override + public void checkRoles(RoleChecker checker) throws SecurityException { + // Deliberately empty + } + } + + private static class EvilCallable implements Callable { + @Override + public Object call() throws Exception { + return null; + } + @Override + public void checkRoles(RoleChecker checker) throws SecurityException { + checker.check(this); // Never do this + } + } + + private static class GoodCallable implements Callable { + @Override + public Object call() throws Exception { + return null; + } + @Override + public void checkRoles(RoleChecker checker) throws SecurityException { + checker.check(this, Roles.MASTER); // Manual S2M + } + } + + + private static SecurityException assertThrowsIOExceptionCausedBySecurityException(ThrowingRunnable runnable) { + return assertThrowsIOExceptionCausedBy(SecurityException.class, runnable); + } + + private static X assertThrowsIOExceptionCausedBy(Class causeClass, ThrowingRunnable runnable) { + try { + runnable.run(); + } catch (IOException ex) { + final Throwable cause = ex.getCause(); + assertTrue("IOException with message: '" + ex.getMessage() + "' wasn't caused by " + causeClass + ": " + (cause == null ? "(null)" : (cause.getClass().getName() + ": " + cause.getMessage())), + cause != null && causeClass.isAssignableFrom(cause.getClass())); + return causeClass.cast(cause); + } catch (Throwable t) { + fail("Threw other Throwable: " + t.getClass() + " with message " + t.getMessage()); + } + fail("Expected exception but passed"); + return null; + } +} From 63cde2daadc705edf086f2213b48c8c547f98358 Mon Sep 17 00:00:00 2001 From: Daniel Beck Date: Mon, 25 Oct 2021 12:52:23 +0000 Subject: [PATCH 05/44] [SECURITY-2455] --- core/src/main/java/hudson/FilePath.java | 291 ++++--- .../main/java/jenkins/SoloFilePathFilter.java | 38 +- .../security/s2m/FilePathRuleConfig.java | 24 +- .../jenkins/security/Security2455Test.java | 756 ++++++++++++++++++ .../security/s2m/AdminFilePathFilterTest.java | 14 +- .../security/Security2455Test/symlink.tar | Bin 0 -> 1536 bytes .../Security2455Test/unzipRemoteTest/file.zip | Bin 0 -> 478 bytes 7 files changed, 991 insertions(+), 132 deletions(-) create mode 100644 test/src/test/java/jenkins/security/Security2455Test.java create mode 100644 test/src/test/resources/jenkins/security/Security2455Test/symlink.tar create mode 100644 test/src/test/resources/jenkins/security/Security2455Test/unzipRemoteTest/file.zip diff --git a/core/src/main/java/hudson/FilePath.java b/core/src/main/java/hudson/FilePath.java index 9c5bb69e044a..3245ae63c049 100644 --- a/core/src/main/java/hudson/FilePath.java +++ b/core/src/main/java/hudson/FilePath.java @@ -34,7 +34,6 @@ 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.Nullable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.Launcher.LocalLauncher; import hudson.Launcher.RemoteLauncher; @@ -216,6 +215,11 @@ public final class FilePath implements SerializableOnlyOverRemoting { */ private static final int MAX_REDIRECTS = 20; + /** + * Escape hatch for some additional protections against sending callables intended to be locally used only + */ + private static /* non-final for Groovy */ boolean REJECT_LOCAL_CALLABLE_DESERIALIZATION = SystemProperties.getBoolean(FilePath.class.getName() + ".rejectLocalCallableDeserialization", true); + /** * When this {@link FilePath} represents the remote path, * this field is always non-null on the controller (the field represents @@ -239,18 +243,6 @@ public final class FilePath implements SerializableOnlyOverRemoting { */ private /*final*/ String remote; - /** - * If this {@link FilePath} is deserialized to handle file access request from a remote computer, - * this field is set to the filter that performs access control. - * - *

- * If null, no access control is needed. - * - * @see #filterNonNull() - */ - private transient @Nullable - SoloFilePathFilter filter; - /** * Creates a {@link FilePath} that represents a path on the given node. * @@ -527,7 +519,7 @@ public int archive(final ArchiverFactory factory, OutputStream os, final DirScan final OutputStream out = channel != null ? new RemoteOutputStream(os) : os; return act(new Archive(factory, out, scanner, verificationRoot, noFollowLinks)); } - private class Archive extends SecureFileCallable { + private static class Archive extends SecureFileCallable { private final ArchiverFactory factory; private final OutputStream out; private final DirScanner scanner; @@ -573,14 +565,14 @@ public int archive(final ArchiverFactory factory, OutputStream os, final String */ public void unzip(final FilePath target) throws IOException, InterruptedException { // TODO: post release, re-unite two branches by introducing FileStreamCallable that resolves InputStream - if (this.channel!=target.channel) {// local -> remote or remote->local + if (channel != target.channel) {// local -> remote or remote->local final RemoteInputStream in = new RemoteInputStream(read(), Flag.GREEDY); target.act(new UnzipRemote(in)); } else {// local -> local or remote->remote - target.act(new UnzipLocal()); + target.act(new UnzipLocal(this)); } } - private class UnzipRemote extends SecureFileCallable { + private static class UnzipRemote extends SecureFileCallable { private final RemoteInputStream in; UnzipRemote(RemoteInputStream in) { this.in = in; @@ -592,14 +584,30 @@ public Void invoke(File dir, VirtualChannel channel) throws IOException, Interru } private static final long serialVersionUID = 1L; } - private class UnzipLocal extends SecureFileCallable { + private static class UnzipLocal extends SecureFileCallable { + + private final FilePath filePath; + + private UnzipLocal(FilePath filePath) { + this.filePath = filePath; + } + @Override public Void invoke(File dir, VirtualChannel channel) throws IOException, InterruptedException { - assert !FilePath.this.isRemote(); // this.channel==target.channel above - unzip(dir, reading(new File(FilePath.this.getRemote()))); // shortcut to local file + if (this.filePath.isRemote()) { + throw new IllegalStateException("Expected local path for file: " + filePath); // this.channel==target.channel above + } + unzip(dir, reading(new File(this.filePath.getRemote()))); // shortcut to local file return null; } private static final long serialVersionUID = 1L; + + protected Object readResolve() { + if (REJECT_LOCAL_CALLABLE_DESERIALIZATION) { + throw new IllegalStateException("This callable is not intended to be sent through a channel"); + } + return this; + } } /** @@ -613,39 +621,52 @@ public Void invoke(File dir, VirtualChannel channel) throws IOException, Interru * @see #untarFrom(InputStream, TarCompression) */ public void untar(final FilePath target, final TarCompression compression) throws IOException, InterruptedException { + final FilePath source = FilePath.this; // TODO: post release, re-unite two branches by introducing FileStreamCallable that resolves InputStream - if (this.channel!=target.channel) {// local -> remote or remote->local - final RemoteInputStream in = new RemoteInputStream(read(), Flag.GREEDY); - target.act(new UntarRemote(compression, in)); + if (source.channel != target.channel) {// local -> remote or remote->local + final RemoteInputStream in = new RemoteInputStream(source.read(), Flag.GREEDY); + target.act(new UntarRemote(source.getName(), compression, in)); } else {// local -> local or remote->remote - target.act(new UntarLocal(compression)); + target.act(new UntarLocal(source, compression)); } } - private class UntarRemote extends SecureFileCallable { + private static class UntarRemote extends SecureFileCallable { private final TarCompression compression; private final RemoteInputStream in; - UntarRemote(TarCompression compression, RemoteInputStream in) { + private final String name; + UntarRemote(String name, TarCompression compression, RemoteInputStream in) { this.compression = compression; this.in = in; + this.name = name; } @Override public Void invoke(File dir, VirtualChannel channel) throws IOException, InterruptedException { - readFromTar(FilePath.this.getName(), dir, compression.extract(in)); + readFromTar(name, dir, compression.extract(in)); return null; } private static final long serialVersionUID = 1L; } - private class UntarLocal extends SecureFileCallable { + private static class UntarLocal extends SecureFileCallable { private final TarCompression compression; - UntarLocal(TarCompression compression) { + private final FilePath filePath; + + UntarLocal(FilePath source, TarCompression compression) { + this.filePath = source; this.compression = compression; } @Override public Void invoke(File dir, VirtualChannel channel) throws IOException, InterruptedException { - readFromTar(FilePath.this.getName(), dir, compression.extract(FilePath.this.read())); + readFromTar(this.filePath.getName(), dir, compression.extract(this.filePath.read())); return null; } private static final long serialVersionUID = 1L; + + protected Object readResolve() { + if (REJECT_LOCAL_CALLABLE_DESERIALIZATION) { + throw new IllegalStateException("This callable is not intended to be sent through a channel"); + } + return this; + } } /** @@ -660,7 +681,7 @@ public void unzipFrom(InputStream _in) throws IOException, InterruptedException final InputStream in = new RemoteInputStream(_in, Flag.GREEDY); act(new UnzipFrom(in)); } - private class UnzipFrom extends SecureFileCallable { + private static class UnzipFrom extends SecureFileCallable { private final InputStream in; UnzipFrom(InputStream in) { this.in = in; @@ -673,7 +694,7 @@ public Void invoke(File dir, VirtualChannel channel) throws IOException { private static final long serialVersionUID = 1L; } - private void unzip(File dir, InputStream in) throws IOException { + private static void unzip(File dir, InputStream in) throws IOException { File tmpFile = File.createTempFile("tmpzip", null); // uses java.io.tmpdir try { // TODO why does this not simply use ZipInputStream? @@ -685,7 +706,7 @@ private void unzip(File dir, InputStream in) throws IOException { } } - private void unzip(File dir, File zipFile) throws IOException { + private static void unzip(File dir, File zipFile) throws IOException { dir = dir.getAbsoluteFile(); // without absolutization, getParentFile below seems to fail ZipFile zip = new ZipFile(zipFile); Enumeration entries = zip.getEntries(); @@ -734,7 +755,7 @@ private static class Absolutize extends SecureFileCallable { private static final long serialVersionUID = 1L; @Override public String invoke(File f, VirtualChannel channel) throws IOException { - return f.getAbsolutePath(); + return stating(f).getAbsolutePath(); } } @@ -754,7 +775,7 @@ private static class HasSymlink extends SecureFileCallable { @Override public Boolean invoke(File f, VirtualChannel channel) throws IOException { - return isSymlink(f, verificationRoot, noFollowLinks); + return isSymlink(stating(f), verificationRoot, noFollowLinks); } } @@ -792,7 +813,7 @@ public boolean accept(File file) { public void symlinkTo(final String target, final TaskListener listener) throws IOException, InterruptedException { act(new SymlinkTo(target, listener)); } - private class SymlinkTo extends SecureFileCallable { + private static class SymlinkTo extends SecureFileCallable { private final String target; private final TaskListener listener; SymlinkTo(String target, TaskListener listener) { @@ -802,8 +823,7 @@ private class SymlinkTo extends SecureFileCallable { private static final long serialVersionUID = 1L; @Override public Void invoke(File f, VirtualChannel channel) throws IOException, InterruptedException { - symlinking(f); - Util.createSymlink(f.getParentFile(), target, f.getName(), listener); + Util.createSymlink(symlinking(f).getParentFile(), target, f.getName(), listener); return null; } } @@ -818,7 +838,7 @@ public Void invoke(File f, VirtualChannel channel) throws IOException, Interrupt public String readLink() throws IOException, InterruptedException { return act(new ReadLink()); } - private class ReadLink extends SecureFileCallable { + private static class ReadLink extends SecureFileCallable { private static final long serialVersionUID = 1L; @Override public String invoke(File f, VirtualChannel channel) throws IOException, InterruptedException { @@ -896,7 +916,7 @@ public void untarFrom(InputStream _in, final TarCompression compression) throws _in.close(); } } - private class UntarFrom extends SecureFileCallable { + private static class UntarFrom extends SecureFileCallable { private final TarCompression compression; private final InputStream in; UntarFrom(TarCompression compression, InputStream in) { @@ -905,7 +925,7 @@ private class UntarFrom extends SecureFileCallable { } @Override public Void invoke(File dir, VirtualChannel channel) throws IOException { - readFromTar("input stream",dir, compression.extract(in)); + readFromTar("input stream",dir, compression.extract(in)); // #writing etc. are called in #readFromTar return null; } private static final long serialVersionUID = 1L; @@ -1157,7 +1177,7 @@ private T act(final FileCallable callable, ClassLoader cl) throws IOExcep if(channel!=null) { // run this on a remote system try { - DelegatingCallable wrapper = new FileCallableWrapper<>(callable, cl); + DelegatingCallable wrapper = new FileCallableWrapper<>(callable, cl, this); for (FileCallableWrapperFactory factory : ExtensionList.lookup(FileCallableWrapperFactory.class)) { wrapper = factory.wrap(wrapper); } @@ -1233,7 +1253,7 @@ protected void after() {} */ public Future actAsync(final FileCallable callable) throws IOException, InterruptedException { try { - DelegatingCallable wrapper = new FileCallableWrapper<>(callable); + DelegatingCallable wrapper = new FileCallableWrapper<>(callable, this); for (FileCallableWrapperFactory factory : ExtensionList.lookup(FileCallableWrapperFactory.class)) { wrapper = factory.wrap(wrapper); } @@ -1302,7 +1322,7 @@ private static class ToURI extends SecureFileCallable { private static final long serialVersionUID = 1L; @Override public URI invoke(File f, VirtualChannel channel) { - return f.toURI(); + return stating(f).toURI(); } } @@ -1340,7 +1360,7 @@ public void mkdirs() throws IOException, InterruptedException { throw new IOException("Failed to mkdirs: " + remote); } } - private class Mkdirs extends SecureFileCallable { + private static class Mkdirs extends SecureFileCallable { private static final long serialVersionUID = 1L; @Override public Boolean invoke(File f, VirtualChannel channel) throws IOException, InterruptedException { @@ -1366,7 +1386,7 @@ public void deleteSuffixesRecursive() throws IOException, InterruptedException { /** * Deletes all suffixed directories that are separated by {@link WorkspaceList#COMBINATOR}, including all its contents recursively. */ - private class DeleteSuffixesRecursive extends SecureFileCallable { + private static class DeleteSuffixesRecursive extends SecureFileCallable { private static final long serialVersionUID = 1L; @Override @@ -1398,7 +1418,7 @@ private static File[] listParentFiles(File f) { public void deleteRecursive() throws IOException, InterruptedException { act(new DeleteRecursive()); } - private class DeleteRecursive extends SecureFileCallable { + private static class DeleteRecursive extends SecureFileCallable { private static final long serialVersionUID = 1L; @Override public Void invoke(File f, VirtualChannel channel) throws IOException { @@ -1413,7 +1433,7 @@ public Void invoke(File f, VirtualChannel channel) throws IOException { public void deleteContents() throws IOException, InterruptedException { act(new DeleteContents()); } - private class DeleteContents extends SecureFileCallable { + private static class DeleteContents extends SecureFileCallable { private static final long serialVersionUID = 1L; @Override public Void invoke(File f, VirtualChannel channel) throws IOException { @@ -1516,7 +1536,7 @@ public FilePath createTempFile(final String prefix, final String suffix) throws throw new IOException("Failed to create a temp file on "+remote,e); } } - private class CreateTempFile extends SecureFileCallable { + private static class CreateTempFile extends SecureFileCallable { private final String prefix; private final String suffix; CreateTempFile(String prefix, String suffix) { @@ -1526,7 +1546,8 @@ private class CreateTempFile extends SecureFileCallable { private static final long serialVersionUID = 1L; @Override public String invoke(File dir, VirtualChannel channel) throws IOException { - File f = writing(File.createTempFile(prefix, suffix, dir)); + creating(new File(dir, prefix + "-security-check-dummy-" + suffix)); // use fake file to check access before creation + File f = creating(File.createTempFile(prefix, suffix, dir)); return f.getName(); } } @@ -1580,7 +1601,7 @@ public FilePath createTextTempFile(final String prefix, final String suffix, fin throw new IOException("Failed to create a temp file on "+remote,e); } } - private final class CreateTextTempFile extends SecureFileCallable { + private static class CreateTextTempFile extends SecureFileCallable { private static final long serialVersionUID = 1L; private final boolean inThisDirectory; private final String prefix; @@ -1601,6 +1622,7 @@ public String invoke(File dir, VirtualChannel channel) throws IOException { File f; try { + creating(new File(dir, prefix + "-security-check-dummy-" + suffix)); // use fake file to check access before creation f = creating(File.createTempFile(prefix, suffix, dir)); } catch (IOException e) { throw new IOException("Failed to create a temporary directory in "+dir,e); @@ -1642,7 +1664,7 @@ public FilePath createTempDir(final String prefix, final String suffix) throws I throw new IOException("Failed to create a temp directory on "+remote,e); } } - private class CreateTempDir extends SecureFileCallable { + private static class CreateTempDir extends SecureFileCallable { private final String name; CreateTempDir(String name) { this.name = name; @@ -1650,6 +1672,7 @@ private class CreateTempDir extends SecureFileCallable { private static final long serialVersionUID = 1L; @Override public String invoke(File dir, VirtualChannel channel) throws IOException { + mkdirsing(new File(dir, name + "-security-test")); // ensure access Path tempPath; final boolean isPosix = FileSystems.getDefault().supportedFileAttributeViews().contains("posix"); @@ -1661,8 +1684,8 @@ public String invoke(File dir, VirtualChannel channel) throws IOException { tempPath = Files.createTempDirectory(Util.fileToPath(dir), name); } - if (tempPath.toFile() == null) { - throw new IOException("Failed to obtain file from path " + dir + " on " + remote); + if (mkdirsing(tempPath.toFile()) == null) { + throw new IOException("Failed to obtain file from path " + dir); } return tempPath.toFile().getName(); } @@ -1677,7 +1700,7 @@ public boolean delete() throws IOException, InterruptedException { act(new Delete()); return true; } - private class Delete extends SecureFileCallable { + private static class Delete extends SecureFileCallable { private static final long serialVersionUID = 1L; @Override public Void invoke(File f, VirtualChannel channel) throws IOException { @@ -1692,7 +1715,7 @@ public Void invoke(File f, VirtualChannel channel) throws IOException { public boolean exists() throws IOException, InterruptedException { return act(new Exists()); } - private class Exists extends SecureFileCallable { + private static class Exists extends SecureFileCallable { private static final long serialVersionUID = 1L; @Override public Boolean invoke(File f, VirtualChannel channel) throws IOException { @@ -1710,7 +1733,7 @@ public Boolean invoke(File f, VirtualChannel channel) throws IOException { public long lastModified() throws IOException, InterruptedException { return act(new LastModified()); } - private class LastModified extends SecureFileCallable { + private static class LastModified extends SecureFileCallable { private static final long serialVersionUID = 1L; @Override public Long invoke(File f, VirtualChannel channel) throws IOException { @@ -1726,7 +1749,7 @@ public Long invoke(File f, VirtualChannel channel) throws IOException { public void touch(final long timestamp) throws IOException, InterruptedException { act(new Touch(timestamp)); } - private class Touch extends SecureFileCallable { + private static class Touch extends SecureFileCallable { private final long timestamp; Touch(long timestamp) { this.timestamp = timestamp; @@ -1750,7 +1773,7 @@ private void setLastModifiedIfPossible(final long timestamp) throws IOException, LOGGER.warning(message); } } - private class SetLastModified extends SecureFileCallable { + private static class SetLastModified extends SecureFileCallable { private final long timestamp; SetLastModified(long timestamp) { this.timestamp = timestamp; @@ -1777,7 +1800,7 @@ public String invoke(File f, VirtualChannel channel) throws IOException { public boolean isDirectory() throws IOException, InterruptedException { return act(new IsDirectory()); } - private final class IsDirectory extends SecureFileCallable { + private static class IsDirectory extends SecureFileCallable { private static final long serialVersionUID = 1L; @Override public Boolean invoke(File f, VirtualChannel channel) throws IOException { @@ -1793,7 +1816,7 @@ public Boolean invoke(File f, VirtualChannel channel) throws IOException { public long length() throws IOException, InterruptedException { return act(new Length()); } - private class Length extends SecureFileCallable { + private static class Length extends SecureFileCallable { private static final long serialVersionUID = 1L; @Override public Long invoke(File f, VirtualChannel channel) throws IOException { @@ -1808,7 +1831,7 @@ public Long invoke(File f, VirtualChannel channel) throws IOException { public long getFreeDiskSpace() throws IOException, InterruptedException { return act(new GetFreeDiskSpace()); } - private static class GetFreeDiskSpace extends SecureFileCallable { + private static class GetFreeDiskSpace extends MasterToSlaveFileCallable { private static final long serialVersionUID = 1L; @Override public Long invoke(File f, VirtualChannel channel) throws IOException { @@ -1823,7 +1846,7 @@ public Long invoke(File f, VirtualChannel channel) throws IOException { public long getTotalDiskSpace() throws IOException, InterruptedException { return act(new GetTotalDiskSpace()); } - private static class GetTotalDiskSpace extends SecureFileCallable { + private static class GetTotalDiskSpace extends MasterToSlaveFileCallable { private static final long serialVersionUID = 1L; @Override public Long invoke(File f, VirtualChannel channel) throws IOException { @@ -1838,7 +1861,7 @@ public Long invoke(File f, VirtualChannel channel) throws IOException { public long getUsableDiskSpace() throws IOException, InterruptedException { return act(new GetUsableDiskSpace()); } - private static class GetUsableDiskSpace extends SecureFileCallable { + private static class GetUsableDiskSpace extends MasterToSlaveFileCallable { private static final long serialVersionUID = 1L; @Override public Long invoke(File f, VirtualChannel channel) throws IOException { @@ -1870,7 +1893,7 @@ public void chmod(final int mask) throws IOException, InterruptedException { if(!isUnix() || mask==-1) return; act(new Chmod(mask)); } - private class Chmod extends SecureFileCallable { + private static class Chmod extends SecureFileCallable { private static final long serialVersionUID = 1L; private final int mask; Chmod(int mask) { @@ -1909,7 +1932,7 @@ public int mode() throws IOException, InterruptedException, PosixException { if(!isUnix()) return -1; return act(new Mode()); } - private class Mode extends SecureFileCallable { + private static class Mode extends SecureFileCallable { private static final long serialVersionUID = 1L; @Override public Integer invoke(File f, VirtualChannel channel) throws IOException { @@ -1979,7 +2002,7 @@ public List list(final FileFilter filter) throws IOException, Interrup } return act(new ListFilter(filter), (filter != null ? filter : this).getClass().getClassLoader()); } - private class ListFilter extends SecureFileCallable> { + private static class ListFilter extends SecureFileCallable> { private final FileFilter filter; ListFilter(FileFilter filter) { this.filter = filter; @@ -2045,7 +2068,7 @@ public FilePath[] list(final String includes, final String excludes) throws IOEx public FilePath[] list(final String includes, final String excludes, final boolean defaultExcludes) throws IOException, InterruptedException { return act(new ListGlob(includes, excludes, defaultExcludes)); } - private class ListGlob extends SecureFileCallable { + private static class ListGlob extends SecureFileCallable { private final String includes; private final String excludes; private final boolean defaultExcludes; @@ -2061,7 +2084,7 @@ public FilePath[] invoke(File f, VirtualChannel channel) throws IOException { FilePath[] r = new FilePath[files.length]; for( int i=0; i { + private static class Read extends SecureFileCallable { private static final long serialVersionUID = 1L; private final Pipe p; private String verificationRoot; @@ -2251,7 +2274,7 @@ public int read(byte[] b) throws IOException { return new java.util.zip.GZIPInputStream(p.getIn()); } - private class OffsetPipeSecureFileCallable extends SecureFileCallable { + private static class OffsetPipeSecureFileCallable extends SecureFileCallable { private static final long serialVersionUID = 1L; private Pipe p; @@ -2284,7 +2307,7 @@ public Void invoke(File f, VirtualChannel channel) throws IOException { public String readToString() throws IOException, InterruptedException { return act(new ReadToString()); } - private final class ReadToString extends SecureFileCallable { + private static class ReadToString extends SecureFileCallable { private static final long serialVersionUID = 1L; @Override public String invoke(File f, VirtualChannel channel) throws IOException, InterruptedException { @@ -2308,12 +2331,12 @@ public OutputStream write() throws IOException, InterruptedException { if(channel==null) { File f = new File(remote).getAbsoluteFile(); mkdirs(f.getParentFile()); - return Files.newOutputStream(fileToPath(writing(f))); + return Files.newOutputStream(fileToPath(writing(f))); // TODO #writing seems unnecessary on a local file } return act(new WritePipe()); } - private class WritePipe extends SecureFileCallable { + private static class WritePipe extends SecureFileCallable { private static final long serialVersionUID = 1L; @Override public OutputStream invoke(File f, VirtualChannel channel) throws IOException, InterruptedException { @@ -2333,7 +2356,7 @@ public OutputStream invoke(File f, VirtualChannel channel) throws IOException, I public void write(final String content, final String encoding) throws IOException, InterruptedException { act(new Write(encoding, content)); } - private class Write extends SecureFileCallable { + private static class Write extends SecureFileCallable { private static final long serialVersionUID = 1L; private final String encoding; private final String content; @@ -2359,7 +2382,7 @@ public Void invoke(File f, VirtualChannel channel) throws IOException { public String digest() throws IOException, InterruptedException { return act(new Digest()); } - private class Digest extends SecureFileCallable { + private static class Digest extends SecureFileCallable { private static final long serialVersionUID = 1L; @Override public String invoke(File f, VirtualChannel channel) throws IOException { @@ -2377,7 +2400,7 @@ public void renameTo(final FilePath target) throws IOException, InterruptedExcep } act(new RenameTo(target)); } - private class RenameTo extends SecureFileCallable { + private static class RenameTo extends SecureFileCallable { private final FilePath target; RenameTo(FilePath target) { this.target = target; @@ -2385,7 +2408,7 @@ private class RenameTo extends SecureFileCallable { private static final long serialVersionUID = 1L; @Override public Void invoke(File f, VirtualChannel channel) throws IOException { - Files.move(fileToPath(reading(f)), fileToPath(creating(new File(target.remote))), LinkOption.NOFOLLOW_LINKS); + Files.move(fileToPath(deleting(reading(f))), fileToPath(writing(creating(new File(target.remote)))), LinkOption.NOFOLLOW_LINKS); return null; } } @@ -2401,7 +2424,7 @@ public void moveAllChildrenTo(final FilePath target) throws IOException, Interru } act(new MoveAllChildrenTo(target)); } - private class MoveAllChildrenTo extends SecureFileCallable { + private static class MoveAllChildrenTo extends SecureFileCallable { private final FilePath target; MoveAllChildrenTo(FilePath target) { this.target = target; @@ -2412,14 +2435,14 @@ public Void invoke(File f, VirtualChannel channel) throws IOException { // JENKINS-16846: if f.getName() is the same as one of the files/directories in f, // then the rename op will fail File tmp = new File(f.getAbsolutePath()+".__rename"); - if (!f.renameTo(tmp)) + if (!deleting(f).renameTo(creating(tmp))) throw new IOException("Failed to rename "+f+" to "+tmp); File t = new File(target.getRemote()); for(File child : reading(tmp).listFiles()) { File target = new File(t, child.getName()); - if(!stating(child).renameTo(creating(target))) + if(!deleting(reading(child)).renameTo(writing(creating(target)))) throw new IOException("Failed to rename "+child+" to "+target); } deleting(tmp).delete(); @@ -2456,7 +2479,7 @@ public void copyToWithPermission(FilePath target) throws IOException, Interrupte target.chmod(mode()); target.setLastModifiedIfPossible(lastModified()); } - private class CopyToWithPermission extends SecureFileCallable { + private static class CopyToWithPermission extends SecureFileCallable { private final FilePath target; CopyToWithPermission(FilePath target) { this.target = target; @@ -2484,7 +2507,7 @@ public void copyTo(OutputStream os) throws IOException, InterruptedException { // this is needed because I/O operation is asynchronous syncIO(); } - private class CopyTo extends SecureFileCallable { + private static class CopyTo extends SecureFileCallable { private static final long serialVersionUID = 4088559042349254141L; private final OutputStream out; CopyTo(OutputStream out) { @@ -2616,7 +2639,7 @@ public int copyRecursiveTo(final DirScanner scanner, final FilePath target, fina // local -> remote copy final Pipe pipe = Pipe.createLocalToRemote(); - Future future = target.actAsync(new ReadToTar(pipe, description, compression)); + Future future = target.actAsync(new ReadFromTar(target, pipe, description, compression)); Future future2 = actAsync(new WriteToTar(scanner, pipe, compression)); try { // JENKINS-9540 in case the reading side failed, report that error first @@ -2662,7 +2685,7 @@ private IOException ioWithCause(ExecutionException e) { ; } - private class CopyRecursiveLocal extends SecureFileCallable { + private static class CopyRecursiveLocal extends SecureFileCallable { private final FilePath target; private final DirScanner scanner; CopyRecursiveLocal(FilePath target, DirScanner scanner) { @@ -2675,7 +2698,9 @@ public Integer invoke(File base, VirtualChannel channel) throws IOException { if (!base.exists()) { return 0; } - assert target.channel == null; + if (target.channel != null) { + throw new IllegalStateException("Expected null channel for " + target); + } final File dest = new File(target.remote); final AtomicInteger count = new AtomicInteger(); scanner.scan(base, reading(new FileVisitor() { @@ -2729,12 +2754,14 @@ public void visitSymlink(File link, String target, String relativePath) throws I return count.get(); } } - private class ReadToTar extends SecureFileCallable { + private static class ReadFromTar extends SecureFileCallable { private final Pipe pipe; private final String description; private final TarCompression compression; + private final FilePath target; - ReadToTar(Pipe pipe, String description, @NonNull TarCompression compression) { + ReadFromTar(FilePath target, Pipe pipe, String description, @NonNull TarCompression compression) { + this.target = target; this.pipe = pipe; this.description = description; this.compression = compression; @@ -2743,12 +2770,12 @@ private class ReadToTar extends SecureFileCallable { @Override public Void invoke(File f, VirtualChannel channel) throws IOException { try (InputStream in = pipe.getIn()) { - readFromTar(remote + '/' + description, f, compression.extract(in)); + readFromTar(target.remote + '/' + description, f, compression.extract(in)); return null; } } } - private class WriteToTar extends SecureFileCallable { + private static class WriteToTar extends SecureFileCallable { private final DirScanner scanner; private final Pipe pipe; private final TarCompression compression; @@ -2760,10 +2787,10 @@ private class WriteToTar extends SecureFileCallable { private static final long serialVersionUID = 1L; @Override public Integer invoke(File f, VirtualChannel channel) throws IOException, InterruptedException { - return writeToTar(new File(remote), scanner, compression.compress(pipe.getOut())); + return writeToTar(reading(f), scanner, compression.compress(pipe.getOut())); } } - private class CopyRecursiveRemoteToLocal extends SecureFileCallable { + private static class CopyRecursiveRemoteToLocal extends SecureFileCallable { private static final long serialVersionUID = 1L; private final Pipe pipe; private final DirScanner scanner; @@ -2776,7 +2803,7 @@ private class CopyRecursiveRemoteToLocal extends SecureFileCallable { @Override public Integer invoke(File f, VirtualChannel channel) throws IOException { try (OutputStream out = pipe.getOut()) { - return writeToTar(f, scanner, compression.compress(out)); + return writeToTar(reading(f), scanner, compression.compress(out)); } } } @@ -2808,7 +2835,7 @@ public int tar(OutputStream out, DirScanner scanner) throws IOException, Interru * @return * number of files/directories that are written. */ - private Integer writeToTar(File baseDir, DirScanner scanner, OutputStream out) throws IOException { + private static Integer writeToTar(File baseDir, DirScanner scanner, OutputStream out) throws IOException { Archiver tw = ArchiverFactory.TAR.create(out); try { scanner.scan(baseDir,reading(tw)); @@ -2822,14 +2849,15 @@ private Integer writeToTar(File baseDir, DirScanner scanner, OutputStream out) t * 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. */ - private void readFromTar(String name, File baseDir, InputStream in) throws IOException { + private static void readFromTar(String name, File baseDir, InputStream in) throws IOException { + baseDir = baseDir.getCanonicalFile(); // TarInputStream t = new TarInputStream(in); try (TarArchiveInputStream t = new TarArchiveInputStream(in)) { TarArchiveEntry te; while ((te = t.getNextTarEntry()) != null) { - File f = new File(baseDir, te.getName()); - if (!f.toPath().normalize().startsWith(baseDir.toPath())) { + File f = new File(baseDir, te.getName()).getCanonicalFile(); + if (!f.toPath().startsWith(baseDir.toPath())) { throw new IOException( "Tar " + name + " contains illegal file name that breaks out of the target directory: " + te.getName()); } @@ -2838,12 +2866,11 @@ private void readFromTar(String name, File baseDir, InputStream in) throws IOExc } else { File parent = f.getParentFile(); if (parent != null) mkdirs(parent); - writing(f); if (te.isSymbolicLink()) { - new FilePath(f).symlinkTo(te.getLinkName(), TaskListener.NULL); + new FilePath(symlinking(f)).symlinkTo(te.getLinkName(), TaskListener.NULL); } else { - IOUtils.copy(t, f); + IOUtils.copy(t, writing(f)); f.setLastModified(te.getModTime().getTime()); int mode = te.getMode() & 0777; @@ -3280,14 +3307,12 @@ private void readObject(ObjectInputStream ois) throws IOException, ClassNotFound ois.defaultReadObject(); if(ois.readBoolean()) { this.channel = channel; - this.filter = null; } else { this.channel = null; // If the remote channel wants us to create a FilePath that points to a local file, // we need to make sure the access control takes place. - // This covers the immediate case of FileCallables taking FilePath into reference closure implicitly, - // but it also covers more general case of FilePath sent as a return value or argument. - this.filter = SoloFilePathFilter.wrap(FilePathFilter.current()); + // Any FileCallables acting on a deserialized FilePath need to ensure they're subjecting it to + // access control checks like #reading(File) etc. } } @@ -3301,24 +3326,27 @@ private void readObject(ObjectInputStream ois) throws IOException, ClassNotFound /** * Adapts {@link FileCallable} to {@link Callable}. */ - private class FileCallableWrapper implements DelegatingCallable { + private static class FileCallableWrapper implements DelegatingCallable { private final FileCallable callable; private transient ClassLoader classLoader; + private final FilePath filePath; - FileCallableWrapper(FileCallable callable) { + FileCallableWrapper(FileCallable callable, FilePath filePath) { this.callable = callable; this.classLoader = callable.getClass().getClassLoader(); + this.filePath = filePath; } - private FileCallableWrapper(FileCallable callable, ClassLoader classLoader) { + private FileCallableWrapper(FileCallable callable, ClassLoader classLoader, FilePath filePath) { this.callable = callable; this.classLoader = classLoader; + this.filePath = filePath; } @Override public T call() throws IOException { try { - return callable.invoke(new File(remote), Channel.current()); + return callable.invoke(new File(filePath.remote), filePath.channel); } catch (InterruptedException e) { throw new TunneledInterruptedException(e); } @@ -3413,15 +3441,16 @@ public ExplicitlySpecifiedDirScanner(Map files) { @NonNull public static final LocalChannel localChannel = new LocalChannel(threadPoolForRemoting); - private @NonNull SoloFilePathFilter filterNonNull() { - return filter!=null ? filter : UNRESTRICTED; + private static @NonNull SoloFilePathFilter filterNonNull() { + final SoloFilePathFilter filter = SoloFilePathFilter.wrap(FilePathFilter.current()); + return filter != null ? filter : UNRESTRICTED; } /** * Wraps {@link FileVisitor} to notify read access to {@link FilePathFilter}. */ - private FileVisitor reading(final FileVisitor v) { - final FilePathFilter filter = FilePathFilter.current(); + private static FileVisitor reading(final FileVisitor v) { + final FilePathFilter filter = SoloFilePathFilter.wrap(FilePathFilter.current()); if (filter==null) return v; return new FileVisitor() { @@ -3470,7 +3499,7 @@ public boolean understandsSymlink() { /** * Pass through 'f' after ensuring that we can read that file. */ - private File reading(File f) { + private static File reading(File f) { filterNonNull().read(f); return f; } @@ -3478,7 +3507,7 @@ private File reading(File f) { /** * Pass through 'f' after ensuring that we can access the file attributes. */ - private File stating(File f) { + private static File stating(File f) { filterNonNull().stat(f); return f; } @@ -3486,7 +3515,7 @@ private File stating(File f) { /** * Pass through 'f' after ensuring that we can create that file/dir. */ - private File creating(File f) { + private static File creating(File f) { filterNonNull().create(f); return f; } @@ -3494,7 +3523,7 @@ private File creating(File f) { /** * Pass through 'f' after ensuring that we can write to that file. */ - private File writing(File f) { + private static File writing(File f) { FilePathFilter filter = filterNonNull(); if (!f.exists()) filter.create(f); @@ -3505,7 +3534,7 @@ private File writing(File f) { /** * Pass through 'f' after ensuring that we can create that symlink. */ - private File symlinking(File f) { + private static File symlinking(File f) { FilePathFilter filter = filterNonNull(); if (!f.exists()) filter.create(f); @@ -3516,24 +3545,40 @@ private File symlinking(File f) { /** * Pass through 'f' after ensuring that we can delete that file. */ - private File deleting(File f) { + private static File deleting(File f) { filterNonNull().delete(f); return f; } - private boolean mkdirs(File dir) throws IOException { + /** + * Pass through 'f' after ensuring that we can mkdirs that directory. + */ + private static File mkdirsing(File f) { + filterNonNull().mkdirs(f); + return f; + } + + private static boolean mkdirs(File dir) throws IOException { if (dir.exists()) return false; - filterNonNull().mkdirs(dir); + File reference = dir; + while (reference != null && !reference.exists()) { + filterNonNull().mkdirs(reference); + reference = reference.getParentFile(); + } Files.createDirectories(fileToPath(dir)); return true; } - private File mkdirsE(File dir) throws IOException { + private static File mkdirsE(File dir) throws IOException { if (dir.exists()) { return dir; } - filterNonNull().mkdirs(dir); + File reference = dir; + while (reference != null && !reference.exists()) { + filterNonNull().mkdirs(reference); + reference = reference.getParentFile(); + } return IOUtils.mkdirs(dir); } @@ -3561,7 +3606,7 @@ public Boolean invoke(@NonNull File parentFile, @NonNull VirtualChannel channel) throw new IllegalArgumentException("Only a relative path is supported, the given path is absolute: " + potentialChildRelativePath); } - Path parentAbsolutePath = Util.fileToPath(parentFile.getAbsoluteFile()); + Path parentAbsolutePath = Util.fileToPath(stating(parentFile).getAbsoluteFile()); Path parentRealPath; try { if (Functions.isWindows()) { diff --git a/core/src/main/java/jenkins/SoloFilePathFilter.java b/core/src/main/java/jenkins/SoloFilePathFilter.java index 073d222e2d53..079373cf237f 100644 --- a/core/src/main/java/jenkins/SoloFilePathFilter.java +++ b/core/src/main/java/jenkins/SoloFilePathFilter.java @@ -1,8 +1,16 @@ package jenkins; import edu.umd.cs.findbugs.annotations.Nullable; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.FilePath; +import jenkins.util.SystemProperties; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; + import java.io.File; +import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; /** * Variant of {@link FilePathFilter} that assumes it is the sole actor. @@ -13,6 +21,13 @@ * @author Kohsuke Kawaguchi */ public final class SoloFilePathFilter extends FilePathFilter { + + private static final Logger LOGGER = Logger.getLogger(SoloFilePathFilter.class.getName()); + + @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @Restricted(NoExternalUse.class) + public static /* non-final for Groovy */ boolean REDACT_ERRORS = SystemProperties.getBoolean(SoloFilePathFilter.class.getName() + ".redactErrors", true); + private final FilePathFilter base; private SoloFilePathFilter(FilePathFilter base) { @@ -28,8 +43,17 @@ private SoloFilePathFilter(FilePathFilter base) { } private boolean noFalse(String op, File f, boolean b) { - if (!b) - throw new SecurityException("agent may not " + op + " " + f+"\nSee https://www.jenkins.io/redirect/security-144 for more details"); + if (!b) { + final String detailedMessage = "Agent may not '" + op + "' at '" + f + "'. See https://www.jenkins.io/redirect/security-144 for more information."; + if (REDACT_ERRORS) { + // We may end up trying to access file paths indirectly, e.g. FilePath#listFiles starts in an allowed dir but follows symlinks outside, so do not disclose paths in error message + UUID uuid = UUID.randomUUID(); + LOGGER.log(Level.WARNING, () -> uuid + ": " + detailedMessage); + throw new SecurityException("Agent may not access a file path. See the system log for more details about the error ID '" + uuid + "' and https://www.jenkins.io/redirect/security-144 for more information."); + } else { + throw new SecurityException(detailedMessage); + } + } return true; } @@ -49,12 +73,18 @@ public boolean write(File f) throws SecurityException { @Override public boolean symlink(File f) throws SecurityException { - return noFalse("symlink",f,base.write(normalize(f))); + return noFalse("symlink",f,base.symlink(normalize(f))); } @Override public boolean mkdirs(File f) throws SecurityException { - return noFalse("mkdirs",f,base.mkdirs(normalize(f))); + // mkdirs is special because it could operate on parents of the specified path + File reference = normalize(f); + while (reference != null && !reference.exists()) { + noFalse("mkdirs", f, base.mkdirs(reference)); // Pass f as reference into the error to be vague + reference = reference.getParentFile(); + } + return true; } @Override diff --git a/core/src/main/java/jenkins/security/s2m/FilePathRuleConfig.java b/core/src/main/java/jenkins/security/s2m/FilePathRuleConfig.java index 36683b9b811a..9788c24ce508 100644 --- a/core/src/main/java/jenkins/security/s2m/FilePathRuleConfig.java +++ b/core/src/main/java/jenkins/security/s2m/FilePathRuleConfig.java @@ -5,12 +5,16 @@ import hudson.Functions; import hudson.model.Failure; import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import jenkins.model.Jenkins; @@ -21,6 +25,9 @@ * @author Kohsuke Kawaguchi */ class FilePathRuleConfig extends ConfigDirectory> { + + private static final Logger LOGGER = Logger.getLogger(FilePathRuleConfig.class.getName()); + FilePathRuleConfig(File file) { super(file); } @@ -40,10 +47,17 @@ protected FilePathRule parse(String line) { line = line.trim(); if (line.isEmpty()) return null; + // TODO This does not support custom build dir configuration (Jenkins#getRawBuildsDir() etc.) line = line.replace("","/builds/"); line = line.replace("","(?:[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]_[0-9][0-9]-[0-9][0-9]-[0-9][0-9]|[0-9]+)"); line = line.replace("","/jobs/.+"); - line = line.replace("","\\Q"+Jenkins.get().getRootDir().getPath()+"\\E"); + final File jenkinsHome = Jenkins.get().getRootDir(); + try { + line = line.replace("","\\Q" + jenkinsHome.getCanonicalPath() + "\\E"); + } catch (IOException e) { + LOGGER.log(Level.WARNING, e, () -> "Failed to determine canonical path to Jenkins home directory, falling back to configured value: " + jenkinsHome.getPath()); + line = line.replace("","\\Q" + jenkinsHome.getPath() + "\\E"); + } // config file is always /-separated even on Windows, so bring it back to \-separation. // This is done in the context of regex, so it has to be \\, which means in the source code it is \\\\ @@ -77,9 +91,11 @@ public boolean checkFileAccess(String op, File path) throws SecurityException { for (FilePathRule rule : get()) { if (rule.op.matches(op)) { if (pathStr==null) { - // do not canonicalize, so that JENKINS_HOME that spans across - // multiple volumes via symlinks can look logically like one unit. - pathStr = path.getPath(); + try { + pathStr = path.getCanonicalPath(); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } if (isWindows()) // Windows accepts '/' as separator, but for rule matching we want to normalize for consistent comparison pathStr = pathStr.replace('/','\\'); } diff --git a/test/src/test/java/jenkins/security/Security2455Test.java b/test/src/test/java/jenkins/security/Security2455Test.java new file mode 100644 index 000000000000..362d2eb59f6e --- /dev/null +++ b/test/src/test/java/jenkins/security/Security2455Test.java @@ -0,0 +1,756 @@ +package jenkins.security; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.junit.Assume.assumeFalse; +import static org.jvnet.hudson.test.LoggerRule.recorded; + +import hudson.ExtensionList; +import hudson.FilePath; +import hudson.Functions; +import hudson.Util; +import hudson.model.Cause; +import hudson.model.FreeStyleBuild; +import hudson.model.Node; +import hudson.model.TaskListener; +import hudson.remoting.VirtualChannel; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.net.URI; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.logging.Level; +import jenkins.SlaveToMasterFileCallable; +import jenkins.SoloFilePathFilter; +import jenkins.agents.AgentComputerUtil; +import jenkins.security.s2m.AdminWhitelistRule; +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.output.NullOutputStream; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.function.ThrowingRunnable; +import org.jvnet.hudson.test.FlagRule; +import org.jvnet.hudson.test.Issue; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.LoggerRule; +import org.jvnet.hudson.test.recipes.LocalData; + +@SuppressWarnings("ThrowableNotThrown") +@Issue("SECURITY-2455") +public class Security2455Test { + + // TODO After merge, reference the class directly + private static final String SECURITY_2428_KILLSWITCH = "jenkins.security.s2m.RunningBuildFilePathFilter.FAIL"; + + @Rule + public final FlagRule flagRule = FlagRule.systemProperty(SECURITY_2428_KILLSWITCH, "false"); + + @Rule + public JenkinsRule j = new JenkinsRule(); + + @Rule + public LoggerRule logging = new LoggerRule().record(SoloFilePathFilter.class, Level.WARNING); + + @Before + public void setup() { + ExtensionList.lookupSingleton(AdminWhitelistRule.class).setMasterKillSwitch(false); + } + + // -------- + + @Test + @Issue("SECURITY-2427") + public void mkdirsParentsTest() { + final File buildStuff = new File(j.jenkins.getRootDir(), "job/nonexistent/builds/1/foo/bar"); + logging.capture(10); + SecurityException ex = assertThrowsIOExceptionCausedBySecurityException(() -> invokeOnAgent(new MkdirsParentsCallable(buildStuff))); + assertThat(logging, recorded(containsString("foo/bar"))); + assertThat(ex.getMessage(), not(containsString("foo/bar"))); // test error redaction + + SoloFilePathFilter.REDACT_ERRORS = false; + try { + SecurityException ex2 = assertThrowsIOExceptionCausedBySecurityException(() -> invokeOnAgent(new MkdirsParentsCallable(buildStuff))); + assertThat(ex2.getMessage(), containsString("foo/bar")); // test error redaction + } finally { + SoloFilePathFilter.REDACT_ERRORS = true; + } + } + private static class MkdirsParentsCallable extends MasterToSlaveCallable { + private final File file; + + private MkdirsParentsCallable(File file) { + this.file = file; + } + + @Override + public String call() throws Exception { + toFilePathOnController(this.file).mkdirs(); + return null; + } + } + + // -------- + + @Test + @Issue("SECURITY-2444") + public void testNonCanonicalPath() throws Exception { + assumeFalse(Functions.isWindows()); + final FreeStyleBuild build = j.createFreeStyleProject().scheduleBuild2(0, new Cause.UserIdCause()).waitForStart(); + j.waitForCompletion(build); + final File link = new File(build.getRootDir(), "link"); + final File secrets = new File(j.jenkins.getRootDir(), "secrets/master.key"); + Files.createSymbolicLink(link.toPath(), secrets.toPath()); + assertThrowsIOExceptionCausedBySecurityException(() -> invokeOnAgent(new ReadToStringCallable(link))); + } + @Test + @Issue("SECURITY-2444") + public void testNonCanonicalPathOnController() throws Exception { + assumeFalse(Functions.isWindows()); + final FreeStyleBuild build = j.createFreeStyleProject().scheduleBuild2(0, new Cause.UserIdCause()).waitForStart(); + j.waitForCompletion(build); + final File link = new File(build.getRootDir(), "link"); + final File secrets = new File(j.jenkins.getRootDir(), "secrets/master.key"); + Files.createSymbolicLink(link.toPath(), secrets.toPath()); + String result = FilePath.localChannel.call(new ReadToStringCallable(link)); + assertEquals(IOUtils.readLines(new FileReader(secrets)).get(0), result); + } + + private static class ReadToStringCallable extends MasterToSlaveCallable { + + final String abs; + + ReadToStringCallable(File link) { + abs = link.getPath(); + } + + @Override + public String call() throws IOException { + FilePath p = toFilePathOnController(new File(abs)); + try { + return p.readToString(); + } catch (InterruptedException e) { + throw new IOException(e); + } + } + } + + // -------- + + @Test + @Issue({"SECURITY-2446", "SECURITY-2531"}) + // $ tar tvf symlink.tar + // lrwxr-xr-x 0 501 20 0 Oct 5 09:50 foo -> ../../../../secrets + @LocalData + public void testUntaringSymlinksFails() throws Exception { + final FreeStyleBuild freeStyleBuild = j.buildAndAssertSuccess(j.createFreeStyleProject()); + final File symlinkTarFile = new File(j.jenkins.getRootDir(), "symlink.tar"); + final File untarTargetFile = new File(freeStyleBuild.getRootDir(), "foo"); + assertThrowsIOExceptionCausedBySecurityException(() -> invokeOnAgent(new UntarFileCallable(symlinkTarFile, untarTargetFile))); + } + private static final class UntarFileCallable extends MasterToSlaveCallable { + private final File source; + private final File destination; + + private UntarFileCallable(File source, File destination) { + this.source = source; + this.destination = destination; + } + + @Override + public Integer call() throws Exception { + final FilePath sourceFilePath = new FilePath(source); + final FilePath destinationFilePath = toFilePathOnController(destination); + sourceFilePath.untar(destinationFilePath, FilePath.TarCompression.NONE); + return 1; + } + } + + // -------- + + @Test + @Issue("SECURITY-2453") + public void testTarSymlinksThatAreSafe() throws Exception { + assumeFalse(Functions.isWindows()); + final File buildDir = j.buildAndAssertSuccess(j.createFreeStyleProject()).getRootDir(); + // We cannot touch the build dir itself + final File innerDir = new File(buildDir, "dir"); + final File innerDir2 = new File(buildDir, "dir2"); + assertTrue(innerDir.mkdirs()); + assertTrue(innerDir2.mkdirs()); + assertTrue(new File(innerDir2, "the-file").createNewFile()); + Util.createSymlink(innerDir, "../dir2", "link", TaskListener.NULL); + assertTrue(new File(innerDir, "link/the-file").exists()); + final int files = invokeOnAgent(new TarCaller(innerDir)); + assertEquals(1, files); + } + @Test + @Issue("SECURITY-2453") + public void testTarSymlinksOutsideAllowedDirs() throws Exception { + assumeFalse(Functions.isWindows()); + final File buildDir = j.buildAndAssertSuccess(j.createFreeStyleProject()).getRootDir(); + // We cannot touch the build dir itself + final File innerDir = new File(buildDir, "dir"); + assertTrue(innerDir.mkdirs()); + Util.createSymlink(innerDir, "../../../../../secrets", "secrets-link", TaskListener.NULL); + assertTrue(new File(innerDir, "secrets-link/master.key").exists()); + logging.capture(10); + assertThrowsIOExceptionCausedBySecurityException(() -> invokeOnAgent(new TarCaller(innerDir))); + assertThat(logging, recorded(containsString("filepath-filters.d"))); + } + + private static class TarCaller extends MasterToSlaveCallable { + private final File root; + + private TarCaller(File root) { + this.root = root; + } + + @Override + public Integer call() throws Exception { + return toFilePathOnController(root).tar(NullOutputStream.NULL_OUTPUT_STREAM, "**"); + } + } + + // -------- + + @Test + @Issue("SECURITY-2484") + public void zipTest() { + final File secrets = new File(j.jenkins.getRootDir(), "secrets"); + assertTrue(secrets.exists()); + assertThrowsIOExceptionCausedBySecurityException(() -> invokeOnAgent(new ZipTestCallable(secrets))); + } + @Test + @Issue("SECURITY-2484") + public void zipTestController() throws Exception { + final File secrets = new File(j.jenkins.getRootDir(), "secrets"); + assertTrue(secrets.exists()); + FilePath.localChannel.call(new ZipTestCallable(secrets)); + } + + private static class ZipTestCallable extends MasterToSlaveCallable { + private final File file; + + private ZipTestCallable(File file) { + this.file = file; + } + + @Override + public String call() throws Exception { + final File tmp = File.createTempFile("security2455_", ".zip"); + tmp.deleteOnExit(); + toFilePathOnController(file).zip(new FilePath(tmp)); + return tmp.getName(); + } + } + + // -------- + + @Test + @Issue("SECURITY-2485") + @LocalData + public void unzipRemoteTest() { + final File targetDir = j.jenkins.getRootDir(); + final File source = new File(targetDir, "file.zip"); // in this test, controller and agent are on same FS so this works -- file needs to exist but content should not be read + assertTrue(targetDir.exists()); + final List filesBefore = Arrays.asList(Objects.requireNonNull(targetDir.listFiles())); + assertThrowsIOExceptionCausedBySecurityException(() -> invokeOnAgent(new UnzipRemoteTestCallable(targetDir, source))); + final List filesAfter = Arrays.asList(Objects.requireNonNull(targetDir.listFiles())); + // We cannot do a direct comparison here because `logs/` appears during execution + assertEquals(filesBefore.size(), filesAfter.stream().filter(it -> !it.getName().equals("logs")).count()); + } + + private static class UnzipRemoteTestCallable extends MasterToSlaveCallable { + private final File destination; + private final File source; + + private UnzipRemoteTestCallable(File destination, File source) { + this.destination = destination; + this.source = source; + } + + @Override + public String call() throws Exception { + FilePath onAgent = new FilePath(source); + onAgent.unzip(toFilePathOnController(destination)); + return null; + } + } + + // -------- + + @Test + @Issue("SECURITY-2485") + public void testCopyRecursiveFromControllerToAgent() { + IOException ex = assertThrowsIOExceptionCausedBy(IOException.class, () -> invokeOnAgent(new CopyRecursiveToFromControllerToAgentCallable(new FilePath(new File(j.jenkins.getRootDir(), "secrets"))))); + assertThat(Objects.requireNonNull(ex).getMessage(), containsString("Unexpected end of ZLIB input stream")); // TODO this used to say "premature", why the change? + } + private static class CopyRecursiveToFromControllerToAgentCallable extends MasterToSlaveCallable { + private final FilePath controllerFilePath; + + private CopyRecursiveToFromControllerToAgentCallable(FilePath controllerFilePath) { + this.controllerFilePath = controllerFilePath; + } + + @Override + public Integer call() throws Exception { + return controllerFilePath.copyRecursiveTo(new FilePath(Files.createTempDirectory("jenkins-test").toFile())); + } + } + + // -------- + + @Test + @Issue("SECURITY-2485") + public void testCopyRecursiveFromAgentToController() { + assertThrowsIOExceptionCausedBy(SecurityException.class, () -> invokeOnAgent(new CopyRecursiveToFromAgentToControllerCallable(new FilePath(new File(j.jenkins.getRootDir(), "secrets"))))); + } + private static class CopyRecursiveToFromAgentToControllerCallable extends MasterToSlaveCallable { + private final FilePath controllerFilePath; + + private CopyRecursiveToFromAgentToControllerCallable(FilePath controllerFilePath) { + this.controllerFilePath = controllerFilePath; + } + + @Override + public Integer call() throws Exception { + final File localPath = Files.createTempDirectory("jenkins-test").toFile(); + assertTrue(new File(localPath, "tmpfile").createNewFile()); + return new FilePath(localPath).copyRecursiveTo(controllerFilePath); + } + } + + // -------- + + @Test + @Issue("SECURITY-2486") + public void testDecoyWrapper() { + assertThrowsIOExceptionCausedBySecurityException(() -> invokeOnAgent(new ReadToStringBypassCallable(j.jenkins.getRootDir()))); + } + + private static class ReadToStringBypassCallable extends MasterToSlaveCallable { + private final File file; + + private ReadToStringBypassCallable(File file) { + this.file = file; + } + + @Override + public String call() throws Exception { + final Class readToStringClass = Class.forName("hudson.FilePath$ReadToString"); + final Constructor constructor = readToStringClass.getDeclaredConstructor(); // Used to have FilePath.class from non-static context + constructor.setAccessible(true); + + //FilePath agentFilePath = new FilePath(new File("on agent lol")); // only used for the core code before fix + + final SlaveToMasterFileCallable callable = (SlaveToMasterFileCallable) constructor.newInstance(); // agentFilePath + + FilePath controllerFilePath = toFilePathOnController(new File(file, "secrets/master.key")); + final Object returned = controllerFilePath.act(callable); + return (String) returned; + } + } + + // -------- + + @Test + @Issue("SECURITY-2531") + public void testSymlinkCheck() throws Exception { + final File buildDir = j.buildAndAssertSuccess(j.createFreeStyleProject()).getRootDir(); + assertThrowsIOExceptionCausedBySecurityException(() -> invokeOnAgent(new SymlinkCreator(buildDir))); + } + private static class SymlinkCreator extends MasterToSlaveCallable { + private final File baseDir; + private SymlinkCreator(File baseDir) { + this.baseDir = baseDir; + } + + @Override + public String call() throws Exception { + toFilePathOnController(new File(baseDir, "child")).symlinkTo(baseDir.getPath() + "child2", TaskListener.NULL); + return null; + } + } + + // -------- + + // -------- + + @Test + @Issue("SECURITY-2538") // SECURITY-2538 adjacent, confirms that reading, stating etc. is supposed to be possible for userContent + public void testReadUserContent() throws Exception { + invokeOnAgent(new UserContentReader(new File(j.jenkins.getRootDir(), "userContent"))); + } + private static class UserContentReader extends MasterToSlaveCallable { + private final File userContent; + + private UserContentReader(File userContent) { + this.userContent = userContent; + } + + @Override + public String call() throws Exception { + final FilePath userContentFilePath = toFilePathOnController(userContent); + userContentFilePath.lastModified(); + userContentFilePath.zip(NullOutputStream.NULL_OUTPUT_STREAM); + assertThat(userContentFilePath.child("readme.txt").readToString(), containsString(hudson.model.Messages.Hudson_USER_CONTENT_README())); + return null; + } + } + + @Test + @Issue("SECURITY-2538") + public void testRenameTo() throws Exception { + final File buildDir = j.buildAndAssertSuccess(j.createFreeStyleProject()).getRootDir(); + final File userContentDir = new File(j.jenkins.getRootDir(), "userContent"); + final File readme = new File(userContentDir, "readme.txt"); + final File to = new File(buildDir, "readme.txt"); + assertTrue("readme.txt is a file", readme.isFile()); + assertThrowsIOExceptionCausedBySecurityException(() -> invokeOnAgent(new RenameToCaller(readme, to))); + assertTrue("readme.txt is still a file", readme.isFile()); + assertFalse("to does not exist", to.exists()); + } + private static class RenameToCaller extends MasterToSlaveCallable { + private final File from; + private final File to; + + private RenameToCaller(File from, File to) { + this.from = from; + this.to = to; + } + + @Override + public String call() throws Exception { + toFilePathOnController(from).renameTo(toFilePathOnController(to)); + return null; + } + } + + @Test + @Issue("SECURITY-2538") + public void testMoveChildren() throws Exception { + final File buildDir = j.buildAndAssertSuccess(j.createFreeStyleProject()).getRootDir(); + final File userContentDir = new File(j.jenkins.getRootDir(), "userContent"); + // The implementation of MoveAllChildrenTo seems odd and ends up removing the source directory, so work only in subdir of userContent + final File userContentSubDir = new File(userContentDir, "stuff"); + assertTrue(userContentSubDir.mkdirs()); + final File userContentSubDirFileA = new File(userContentSubDir, "fileA"); + final File userContentSubDirFileB = new File(userContentSubDir, "fileB"); + assertTrue(userContentSubDirFileA.createNewFile()); + assertTrue(userContentSubDirFileB.createNewFile()); + assertTrue("userContentSubDir is a directory", userContentSubDir.isDirectory()); + assertThrowsIOExceptionCausedBySecurityException(() -> invokeOnAgent(new MoveAllChildrenToCaller(userContentSubDir, buildDir))); + assertTrue("userContentSubDir is a directory", userContentSubDir.isDirectory()); + assertFalse("no fileA in buildDir", new File(buildDir, "fileA").exists()); + assertFalse("no fileB in buildDir", new File(buildDir, "fileB").exists()); + assertTrue("fileA is still a file", userContentSubDirFileA.isFile()); + assertTrue("fileB is still a file", userContentSubDirFileB.isFile()); + } + private static class MoveAllChildrenToCaller extends MasterToSlaveCallable { + private final File from; + private final File to; + + private MoveAllChildrenToCaller(File from, File to) { + this.from = from; + this.to = to; + } + + @Override + public String call() throws Exception { + toFilePathOnController(from).moveAllChildrenTo(toFilePathOnController(to)); + return null; + } + } + + // -------- + + @Test + @Issue("SECURITY-2539") + public void testCreateTempFile() { + final File rootDir = j.jenkins.getRootDir(); + assertFalse(Arrays.stream(Objects.requireNonNull(rootDir.listFiles())).anyMatch(it -> it.getName().contains("prefix"))); + + // Extra level of catch/throw + logging.capture(10); + final IOException ioException = assertThrowsIOExceptionCausedBy(IOException.class, () -> invokeOnAgent(new CreateTempFileCaller(rootDir))); + assertNotNull(ioException); + final Throwable cause = ioException.getCause(); + assertNotNull(cause); + assertTrue(cause instanceof SecurityException); + assertThat(cause.getMessage(), not(containsString("prefix"))); // redacted + assertThat(logging, recorded(containsString("'create'"))); + assertThat(logging, recorded(containsString("/prefix-security-check-dummy-suffix"))); + assertFalse(Arrays.stream(Objects.requireNonNull(rootDir.listFiles())).anyMatch(it -> it.getName().contains("prefix"))); + } + private static class CreateTempFileCaller extends MasterToSlaveCallable { + private final File baseDir; + + private CreateTempFileCaller(File baseDir) { + this.baseDir = baseDir; + } + + @Override + public String call() throws Exception { + toFilePathOnController(baseDir).createTempFile("prefix", "suffix"); + return null; + } + } + + + @Test + @Issue("SECURITY-2539") + public void testCreateTextTempFile() { + final File rootDir = j.jenkins.getRootDir(); + assertFalse(Arrays.stream(Objects.requireNonNull(rootDir.listFiles())).anyMatch(it -> it.getName().contains("prefix"))); + + // Extra level of catch/throw + logging.capture(10); + final IOException ioException = assertThrowsIOExceptionCausedBy(IOException.class, () -> invokeOnAgent(new CreateTextTempFileCaller(rootDir))); + assertNotNull(ioException); + final Throwable cause = ioException.getCause(); + assertNotNull(cause); + assertTrue(cause instanceof SecurityException); + assertThat(cause.getMessage(), not(containsString("prefix"))); // redacted + assertThat(logging, recorded(containsString("'create'"))); + assertThat(logging, recorded(containsString("/prefix-security-check-dummy-suffix"))); + assertFalse(Arrays.stream(Objects.requireNonNull(rootDir.listFiles())).anyMatch(it -> it.getName().contains("prefix"))); + } + private static class CreateTextTempFileCaller extends MasterToSlaveCallable { + private final File baseDir; + + private CreateTextTempFileCaller(File baseDir) { + this.baseDir = baseDir; + } + + @Override + public String call() throws Exception { + toFilePathOnController(baseDir).createTextTempFile("prefix", "suffix", "content"); + return null; + } + } + + @Test + @Issue("SECURITY-2539") + public void testCreateTempDir() { + final File rootDir = j.jenkins.getRootDir(); + assertFalse(Arrays.stream(Objects.requireNonNull(rootDir.listFiles())).anyMatch(it -> it.getName().contains("prefix"))); + + // Extra level of catch/throw + logging.capture(10); + final IOException ioException = assertThrowsIOExceptionCausedBy(IOException.class, () -> invokeOnAgent(new CreateTempDirCaller(rootDir))); + assertNotNull(ioException); + final Throwable cause = ioException.getCause(); + assertNotNull(cause); + assertTrue(cause instanceof SecurityException); + assertThat(cause.getMessage(), not(containsString("prefix"))); // redacted + assertThat(logging, recorded(containsString("'mkdirs'"))); + assertThat(logging, recorded(containsString("/prefix.suffix-security-test"))); // weird but that's what it looks like + assertFalse(Arrays.stream(Objects.requireNonNull(rootDir.listFiles())).anyMatch(it -> it.getName().contains("prefix"))); + } + private static class CreateTempDirCaller extends MasterToSlaveCallable { + private final File baseDir; + + private CreateTempDirCaller(File baseDir) { + this.baseDir = baseDir; + } + + @Override + public String call() throws Exception { + toFilePathOnController(baseDir).createTempDir("prefix", "suffix"); + return null; + } + } + // -------- + + @Test + @Issue("SECURITY-2541") + public void testStatStuff() { + assertThrowsIOExceptionCausedBySecurityException(() -> invokeOnAgent(new ToURICaller(j.jenkins.getRootDir()))); + assertThrowsIOExceptionCausedBySecurityException(() -> invokeOnAgent(new FreeDiskSpaceCaller(j.jenkins.getRootDir()))); + assertThrowsIOExceptionCausedBySecurityException(() -> invokeOnAgent(new TotalDiskSpaceCaller(j.jenkins.getRootDir()))); + assertThrowsIOExceptionCausedBySecurityException(() -> invokeOnAgent(new UsableDiskSpaceCaller(j.jenkins.getRootDir()))); + assertThrowsIOExceptionCausedBySecurityException(() -> invokeOnAgent(new AbsolutizeCaller(j.jenkins.getRootDir()))); + assertThrowsIOExceptionCausedBySecurityException(() -> invokeOnAgent(new HasSymlinkCaller(new File (j.jenkins.getRootDir(), "secrets"), j.jenkins.getRootDir()))); + assertThrowsIOExceptionCausedBySecurityException(() -> invokeOnAgent(new IsDescendantCaller(j.jenkins.getRootDir(), "secrets"))); + } + + private static class ToURICaller extends MasterToSlaveCallable { + private final File file; + + private ToURICaller(File file) { + this.file = file; + } + + @Override + public URI call() throws Exception { + return toFilePathOnController(file).toURI(); + } + } + private static class AbsolutizeCaller extends MasterToSlaveCallable { + private final File file; + + private AbsolutizeCaller(File file) { + this.file = file; + } + + @Override + public String call() throws Exception { + return toFilePathOnController(file).absolutize().getRemote(); + } + } + private static class HasSymlinkCaller extends MasterToSlaveCallable { + private final File file; + private final File root; + + private HasSymlinkCaller(File file, File root) { + this.file = file; + this.root = root; + } + + @Override + public Boolean call() throws Exception { + return toFilePathOnController(file).hasSymlink(toFilePathOnController(root), false); + } + } + private static class IsDescendantCaller extends MasterToSlaveCallable { + private final File file; + private final String childPath; + + private IsDescendantCaller(File file, String childPath) { + this.file = file; + this.childPath = childPath; + } + + @Override + public Boolean call() throws Exception { + return toFilePathOnController(file).isDescendant(childPath); + } + } + private static class FreeDiskSpaceCaller extends MasterToSlaveCallable { + private final File file; + + private FreeDiskSpaceCaller(File file) { + this.file = file; + } + + @Override + public Long call() throws Exception { + return toFilePathOnController(file).getFreeDiskSpace(); + } + } + private static class UsableDiskSpaceCaller extends MasterToSlaveCallable { + private final File file; + + private UsableDiskSpaceCaller(File file) { + this.file = file; + } + + @Override + public Long call() throws Exception { + return toFilePathOnController(file).getUsableDiskSpace(); + } + } + private static class TotalDiskSpaceCaller extends MasterToSlaveCallable { + private final File file; + + private TotalDiskSpaceCaller(File file) { + this.file = file; + } + + @Override + public Long call() throws Exception { + return toFilePathOnController(file).getTotalDiskSpace(); + } + } + + // -------- + + @Test + @Issue("SECURITY-2542") // adjacent, this confirms we follow symlinks when it's within allowed directories + public void testGlobFollowsSymlinks() throws Exception { + assumeFalse(Functions.isWindows()); + final File buildDir = j.buildAndAssertSuccess(j.createFreeStyleProject()).getRootDir(); + // We cannot touch the build dir itself + final File innerDir = new File(buildDir, "dir"); + final File innerDir2 = new File(buildDir, "dir2"); + assertTrue(innerDir.mkdirs()); + assertTrue(innerDir2.mkdirs()); + assertTrue(new File(innerDir2, "the-file").createNewFile()); + Util.createSymlink(innerDir, "../dir2", "link", TaskListener.NULL); + assertTrue(new File(innerDir, "link/the-file").exists()); + final int files = invokeOnAgent(new GlobCaller(innerDir)); + assertEquals(1, files); + } + @Test + @Issue("SECURITY-2542") + public void testGlobSymlinksThrowsOutsideAllowedDirectories() throws Exception { + assumeFalse(Functions.isWindows()); + final File buildDir = j.buildAndAssertSuccess(j.createFreeStyleProject()).getRootDir(); + // We cannot touch the build dir itself + final File innerDir = new File(buildDir, "dir"); + assertTrue(innerDir.mkdirs()); + Util.createSymlink(innerDir, "../../../../../secrets", "secrets-link", TaskListener.NULL); + assertTrue(new File(innerDir, "secrets-link/master.key").exists()); + assertThrowsIOExceptionCausedBySecurityException(() -> invokeOnAgent(new GlobCaller(innerDir))); + } + private static class GlobCaller extends MasterToSlaveCallable { + private final File root; + + private GlobCaller(File root) { + this.root = root; + } + + @Override + public Integer call() throws Exception { + return toFilePathOnController(root).list("**/*", "", false).length; + } + } + + // -------- + + // Utility functions + + protected static FilePath toFilePathOnController(File file) { + return toFilePathOnController(file.getPath()); + } + + protected static FilePath toFilePathOnController(String path) { + final VirtualChannel channel = AgentComputerUtil.getChannelToMaster(); + return new FilePath(channel, path); + } + + protected T invokeOnAgent(MasterToSlaveCallable callable) throws Exception, X { + final Node agent = j.createOnlineSlave(); + return Objects.requireNonNull(agent.getChannel()).call(callable); + } + + private static SecurityException assertThrowsIOExceptionCausedBySecurityException(ThrowingRunnable runnable) { + return assertThrowsIOExceptionCausedBy(SecurityException.class, runnable); + } + + private static X assertThrowsIOExceptionCausedBy(Class causeClass, ThrowingRunnable runnable) { + try { + runnable.run(); + } catch (IOException ex) { + final Throwable cause = ex.getCause(); + assertTrue("IOException with message: '" + ex.getMessage() + "' wasn't caused by " + causeClass + ": " + (cause == null ? "(null)" : (cause.getClass().getName() + ": " + cause.getMessage())), + cause != null && causeClass.isAssignableFrom(cause.getClass())); + return causeClass.cast(cause); + } catch (Throwable t) { + fail("Threw other Throwable: " + t.getClass() + " with message " + t.getMessage()); + } + fail("Expected exception but passed"); + return null; + } +} diff --git a/test/src/test/java/jenkins/security/s2m/AdminFilePathFilterTest.java b/test/src/test/java/jenkins/security/s2m/AdminFilePathFilterTest.java index fb98068af1b8..4c4d8f41a29a 100644 --- a/test/src/test/java/jenkins/security/s2m/AdminFilePathFilterTest.java +++ b/test/src/test/java/jenkins/security/s2m/AdminFilePathFilterTest.java @@ -24,10 +24,13 @@ package jenkins.security.s2m; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.jvnet.hudson.test.LoggerRule.recorded; import hudson.FilePath; import hudson.model.Slave; @@ -37,19 +40,25 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.Field; +import java.util.logging.Level; import javax.inject.Inject; +import jenkins.SoloFilePathFilter; import org.jenkinsci.remoting.RoleChecker; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.LoggerRule; public class AdminFilePathFilterTest { @Rule public JenkinsRule r = new JenkinsRule(); + @Rule + public LoggerRule logging = new LoggerRule().record(SoloFilePathFilter.class, Level.WARNING); + @Inject AdminWhitelistRule rule; @@ -186,6 +195,7 @@ private void checkSlave_can_readFile(Slave s, FilePath target) throws Exception private void checkSlave_cannot_readFile(Slave s, FilePath target) throws Exception { try { + logging.capture(10); s.getChannel().call(new ReadFileS2MCallable(target)); fail("Slave should not be able to read file in " + target.getRemote()); } catch (IOException e){ @@ -194,7 +204,9 @@ private void checkSlave_cannot_readFile(Slave s, FilePath target) throws Excepti SecurityException se = (SecurityException) t; StringWriter sw = new StringWriter(); se.printStackTrace(new PrintWriter(sw)); - assertTrue(sw.toString().contains("agent may not read")); + assertTrue(sw.toString().contains("Agent may not access a file path")); + + assertThat(logging, recorded(containsString("Agent may not 'read' at"))); } } diff --git a/test/src/test/resources/jenkins/security/Security2455Test/symlink.tar b/test/src/test/resources/jenkins/security/Security2455Test/symlink.tar new file mode 100644 index 0000000000000000000000000000000000000000..de6a1d990b05179c922b72cbd1a5fa9970c718c1 GIT binary patch literal 1536 zcmYex&u5@DFfcGMH#JpY0MTX;+Q7&J%m)gAfr6olp^>?PfuXsHk%EDtk%^H3gMyKs xo<1BErzRJrmK0Ont);~!iA6xCQED*MA0STxfdZw*jp`o_fzc2c4FOt*008eR6fVi}Rn}Lz#D|A62_JlY z!aneabp$jpO^{$#7m#Kwloe?HAuZ9t)TgNE7@+2HoJm2-z;v&3=dXI}Xq@o8 zdfHP*PgB>=*V9wSGn9{mP1EO;zcAkOZIGTXnzIB(NM-0$)AQuLBGct)VqXr7h xU!XvNfhCO~7B&xH2_keuku3xT2@EW03 Date: Mon, 25 Oct 2021 12:52:33 +0000 Subject: [PATCH 06/44] [SECURITY-2455] --- .../security/s2m/FilePathRuleConfig.java | 4 +- .../jenkins/security/s2m/filepath-filter.conf | 8 +- .../jenkins/security/Security2455Test.java | 96 ++++++++++++++++++- 3 files changed, 104 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/jenkins/security/s2m/FilePathRuleConfig.java b/core/src/main/java/jenkins/security/s2m/FilePathRuleConfig.java index 9788c24ce508..c4b111f2bdcb 100644 --- a/core/src/main/java/jenkins/security/s2m/FilePathRuleConfig.java +++ b/core/src/main/java/jenkins/security/s2m/FilePathRuleConfig.java @@ -48,7 +48,9 @@ protected FilePathRule parse(String line) { if (line.isEmpty()) return null; // TODO This does not support custom build dir configuration (Jenkins#getRawBuildsDir() etc.) - line = line.replace("","/builds/"); + line = line.replace("","/builds/[0-9]+"); + + // Kept only for compatibility with custom user-provided rules: line = line.replace("","(?:[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]_[0-9][0-9]-[0-9][0-9]-[0-9][0-9]|[0-9]+)"); line = line.replace("","/jobs/.+"); final File jenkinsHome = Jenkins.get().getRootDir(); diff --git a/core/src/main/resources/jenkins/security/s2m/filepath-filter.conf b/core/src/main/resources/jenkins/security/s2m/filepath-filter.conf index e3a63abf1b28..543c56e83f85 100644 --- a/core/src/main/resources/jenkins/security/s2m/filepath-filter.conf +++ b/core/src/main/resources/jenkins/security/s2m/filepath-filter.conf @@ -38,8 +38,12 @@ deny all /checkpoints($|/.*) # But not allowing deletion to prevent data loss and symlink to prevent jailbreaking. allow create,mkdirs,read,stat,write /.+ -# cobertura also writes out annotated sources to a dir under the job: -allow create,mkdirs,read,stat,write /jobs/.+/cobertura.* +# cobertura also writes out annotated sources to a dir under the Maven module: +allow create,mkdirs,read,stat,write /modules/([^/]+)/cobertura($|/.*) + +# Some maven-plugin reporters also create content outside of build directories (including one in cobertura but that is covered above): +allow create,mkdirs,read,stat,write (/jobs/([^/]+))+(|/modules/([^/]+))/(javadoc|test-javadoc)($|/.*) +allow create,mkdirs,read,stat,write (/jobs/([^/]+))+/site($|/.*) # all the other accesses that aren't specified here will be left up to other rules in this directory. # if no rules in those other files matches, then the access will be rejected. diff --git a/test/src/test/java/jenkins/security/Security2455Test.java b/test/src/test/java/jenkins/security/Security2455Test.java index 362d2eb59f6e..e190b033bffc 100644 --- a/test/src/test/java/jenkins/security/Security2455Test.java +++ b/test/src/test/java/jenkins/security/Security2455Test.java @@ -17,6 +17,7 @@ import hudson.Util; import hudson.model.Cause; import hudson.model.FreeStyleBuild; +import hudson.model.FreeStyleProject; import hudson.model.Node; import hudson.model.TaskListener; import hudson.remoting.VirtualChannel; @@ -44,6 +45,7 @@ import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.LoggerRule; +import org.jvnet.hudson.test.MockFolder; import org.jvnet.hudson.test.recipes.LocalData; @SuppressWarnings("ThrowableNotThrown") @@ -719,6 +721,94 @@ public Integer call() throws Exception { // -------- + @Issue("SECURITY-2455") // general issue -- Maven Projects would no longer be allowed to perform some actions + @Test + public void testMavenReportersAllowListForTopLevelJob() throws Exception { + final FreeStyleProject project = j.createFreeStyleProject(); + final File topLevelProjectDir = project.getRootDir(); + + // similar but wrong names: + assertThrowsIOExceptionCausedBySecurityException(() -> invokeOnAgent(new MkDirsWriter(new File(topLevelProjectDir, "not-site")))); + assertThrowsIOExceptionCausedBySecurityException(() -> invokeOnAgent(new MkDirsWriter(new File(topLevelProjectDir, "not-javadoc")))); + + // project-level archived stuff: + invokeOnAgent(new MkDirsWriter(new File(topLevelProjectDir, "javadoc"))); + invokeOnAgent(new MkDirsWriter(new File(topLevelProjectDir, "test-javadoc"))); + invokeOnAgent(new MkDirsWriter(new File(topLevelProjectDir, "site"))); + assertThrowsIOExceptionCausedBySecurityException(() -> invokeOnAgent(new MkDirsWriter(new File(topLevelProjectDir, "cobertura")))); + + // cannot mkdirs this from agent: + assertThrowsIOExceptionCausedBySecurityException(() -> invokeOnAgent(new MkDirsWriter(new File(topLevelProjectDir, "modules")))); + + final File mavenModuleDir = new File(topLevelProjectDir, "modules/pretend-maven-module"); + assertTrue(mavenModuleDir.mkdirs()); + + // module-level archived stuff: + invokeOnAgent(new MkDirsWriter(new File(mavenModuleDir, "javadoc"))); + invokeOnAgent(new MkDirsWriter(new File(mavenModuleDir, "test-javadoc"))); + assertThrowsIOExceptionCausedBySecurityException(() -> invokeOnAgent(new MkDirsWriter(new File(mavenModuleDir, "site")))); + invokeOnAgent(new MkDirsWriter(new File(mavenModuleDir, "cobertura"))); + } + + @Issue("SECURITY-2455") // general issue -- Maven Projects would no longer be allowed to perform some actions + @Test + public void testMavenReportersAllowListForJobInFolder() throws Exception { + final MockFolder theFolder = j.createFolder("theFolder"); + { + // basic child job + final FreeStyleProject childProject = theFolder.createProject(FreeStyleProject.class, "child"); + final File childProjectRootDir = childProject.getRootDir(); + + // project-level archived stuff for child project inside folder: + invokeOnAgent(new MkDirsWriter(new File(childProjectRootDir, "javadoc"))); + invokeOnAgent(new MkDirsWriter(new File(childProjectRootDir, "test-javadoc"))); + invokeOnAgent(new MkDirsWriter(new File(childProjectRootDir, "site"))); + assertThrowsIOExceptionCausedBySecurityException(() -> invokeOnAgent(new MkDirsWriter(new File(childProjectRootDir, "cobertura")))); + } + + { // misleadingly named child job (like one of the approved folders): + final FreeStyleProject siteChildProject = theFolder.createProject(FreeStyleProject.class, "site"); + final File siteChildProjectRootDir = siteChildProject.getRootDir(); + + // cannot mkdirs this from agent despite 'site' in the path (but on wrong level): + assertThrowsIOExceptionCausedBySecurityException(() -> invokeOnAgent(new MkDirsWriter(siteChildProjectRootDir))); + assertThrowsIOExceptionCausedBySecurityException(() -> invokeOnAgent(new MkDirsWriter(new File(siteChildProjectRootDir, "foo")))); + assertThrowsIOExceptionCausedBySecurityException(() -> invokeOnAgent(new MkDirsWriter(new File(siteChildProjectRootDir, "modules")))); + + // project-level archived stuff for another child inside folder: + invokeOnAgent(new MkDirsWriter(new File(siteChildProjectRootDir, "javadoc"))); + invokeOnAgent(new MkDirsWriter(new File(siteChildProjectRootDir, "test-javadoc"))); + invokeOnAgent(new MkDirsWriter(new File(siteChildProjectRootDir, "site"))); + assertThrowsIOExceptionCausedBySecurityException(() -> invokeOnAgent(new MkDirsWriter(new File(siteChildProjectRootDir, "cobertura")))); + + final File childProjectMavenModuleDir = new File(siteChildProjectRootDir, "modules/pretend-maven-module"); + assertTrue(childProjectMavenModuleDir.mkdirs()); + + // module-level archived stuff: + invokeOnAgent(new MkDirsWriter(new File(childProjectMavenModuleDir, "javadoc"))); + invokeOnAgent(new MkDirsWriter(new File(childProjectMavenModuleDir, "test-javadoc"))); + assertThrowsIOExceptionCausedBySecurityException(() -> invokeOnAgent(new MkDirsWriter(new File(childProjectMavenModuleDir, "site")))); + invokeOnAgent(new MkDirsWriter(new File(childProjectMavenModuleDir, "cobertura"))); + } + } + + private static class MkDirsWriter extends MasterToSlaveCallable { + private final File root; + + private MkDirsWriter(File root) { + this.root = root; + } + + @Override + public Object call() throws Exception { + toFilePathOnController(root).mkdirs(); + toFilePathOnController(new File(root, "file.txt")).write("text", "UTF-8"); + return null; + } + } + + // -------- + // Utility functions protected static FilePath toFilePathOnController(File file) { @@ -730,8 +820,12 @@ protected static FilePath toFilePathOnController(String path) { return new FilePath(channel, path); } + protected Node agent; + protected T invokeOnAgent(MasterToSlaveCallable callable) throws Exception, X { - final Node agent = j.createOnlineSlave(); + if (agent == null) { + agent = j.createOnlineSlave(); + } return Objects.requireNonNull(agent.getChannel()).call(callable); } From 531283c3070a2729c7a0c4ad60f85e836244937f Mon Sep 17 00:00:00 2001 From: Basil Crow Date: Mon, 25 Oct 2021 14:03:25 -0700 Subject: [PATCH 07/44] Stabilize `core` test suite (#5855) --- core/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/pom.xml b/core/pom.xml index 92fcd2fd8e50..694d0b9695d7 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -719,7 +719,7 @@ THE SOFTWARE. 0.5C - true + false From 4e1b5d6aece12f88406ffd2cf8359057def5aaf8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Oct 2021 22:04:46 +0100 Subject: [PATCH 08/44] Bump antisamy-markup-formatter from 2.3 to 2.4 (#5840) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- test/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/pom.xml b/test/pom.xml index 36b24a47cc41..45e69b6b0942 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -117,7 +117,7 @@ THE SOFTWARE. org.jenkins-ci.plugins antisamy-markup-formatter - 2.3 + 2.4 test From 3fdd2f44f313dfea7b9931149b4e3ca3b1902d7d Mon Sep 17 00:00:00 2001 From: Antonio Muniz Date: Mon, 25 Oct 2021 23:05:01 +0200 Subject: [PATCH 09/44] [JENKINS-66955] Add constructor matching super (#5844) Co-authored-by: Jesse Glick --- core/src/main/java/hudson/util/XStream2.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/core/src/main/java/hudson/util/XStream2.java b/core/src/main/java/hudson/util/XStream2.java index 03dcc82c3f71..8d8ead7d835c 100644 --- a/core/src/main/java/hudson/util/XStream2.java +++ b/core/src/main/java/hudson/util/XStream2.java @@ -47,6 +47,9 @@ import com.thoughtworks.xstream.mapper.Mapper; import com.thoughtworks.xstream.mapper.MapperWrapper; import com.thoughtworks.xstream.security.AnyTypePermission; +import com.thoughtworks.xstream.converters.reflection.ReflectionProvider; +import com.thoughtworks.xstream.converters.ConverterLookup; +import com.thoughtworks.xstream.converters.ConverterRegistry; import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.PluginManager; @@ -120,6 +123,17 @@ public XStream2(HierarchicalStreamDriver hierarchicalStreamDriver) { classOwnership = null; } + /** + * @since TODO + */ + public XStream2(ReflectionProvider reflectionProvider, HierarchicalStreamDriver driver, + ClassLoaderReference classLoaderReference, Mapper mapper, ConverterLookup converterLookup, + ConverterRegistry converterRegistry) { + super(reflectionProvider, driver, classLoaderReference, mapper, converterLookup, converterRegistry); + init(); + classOwnership = null; + } + XStream2(ClassOwnership classOwnership) { super(getDefaultDriver()); init(); From e7d52169dca11007f6acb058f233d6bcb3f8ba2f Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Mon, 25 Oct 2021 17:05:20 -0400 Subject: [PATCH 10/44] [JENKINS-66947] Make `ExecutorListener` an extension point (#5841) --- core/src/main/java/hudson/model/Computer.java | 15 -------- .../java/hudson/model/ExecutorListener.java | 19 ++++++++--- .../hudson/model/queue/WorkUnitContext.java | 34 +++++++++++++++++++ .../java/hudson/slaves/SlaveComputer.java | 14 ++++++-- 4 files changed, 60 insertions(+), 22 deletions(-) diff --git a/core/src/main/java/hudson/model/Computer.java b/core/src/main/java/hudson/model/Computer.java index 306e1ae8e260..a01121b2d743 100644 --- a/core/src/main/java/hudson/model/Computer.java +++ b/core/src/main/java/hudson/model/Computer.java @@ -613,21 +613,6 @@ public BuildTimelineWidget getTimeline() { return new BuildTimelineWidget(getBuilds()); } - @Override - public void taskAccepted(Executor executor, Queue.Task task) { - // dummy implementation - } - - @Override - public void taskCompleted(Executor executor, Queue.Task task, long durationMS) { - // dummy implementation - } - - @Override - public void taskCompletedWithProblems(Executor executor, Queue.Task task, long durationMS, Throwable problems) { - // dummy implementation - } - @Exported public boolean isOffline() { return temporarilyOffline || getChannel()==null; diff --git a/core/src/main/java/hudson/model/ExecutorListener.java b/core/src/main/java/hudson/model/ExecutorListener.java index 3c5e221f17f2..754c32976739 100644 --- a/core/src/main/java/hudson/model/ExecutorListener.java +++ b/core/src/main/java/hudson/model/ExecutorListener.java @@ -23,22 +23,33 @@ */ package hudson.model; +import hudson.ExtensionPoint; import hudson.slaves.SlaveComputer; /** * 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). * @author Stephen Connolly * @since 1.312 */ -public interface ExecutorListener { +public interface ExecutorListener extends ExtensionPoint { /** * Called whenever a task is accepted by an executor. * @param executor The executor. * @param task The task. */ - void taskAccepted(Executor executor, Queue.Task task); + default void taskAccepted(Executor executor, Queue.Task task) {} + + /** + * Called whenever a task is started by an executor. + * @param executor The executor. + * @param task The task. + * @param executable the executable. + * @since TODO + */ + default void taskStarted(Executor e, Queue.Task task) {} /** * Called whenever a task is completed without any problems by an executor. @@ -46,7 +57,7 @@ public interface ExecutorListener { * @param task The task. * @param durationMS The number of milliseconds that the task took to complete. */ - void taskCompleted(Executor executor, Queue.Task task, long durationMS); + default void taskCompleted(Executor executor, Queue.Task task, long durationMS) {} /** * Called whenever a task is completed with some problems by an executor. @@ -55,5 +66,5 @@ public interface ExecutorListener { * @param durationMS The number of milliseconds that the task took to complete. * @param problems The exception that was thrown. */ - void taskCompletedWithProblems(Executor executor, Queue.Task task, long durationMS, Throwable problems); + default void taskCompletedWithProblems(Executor executor, Queue.Task task, long durationMS, Throwable problems) {} } diff --git a/core/src/main/java/hudson/model/queue/WorkUnitContext.java b/core/src/main/java/hudson/model/queue/WorkUnitContext.java index 6fffef0184f8..c08d5c6e88ee 100644 --- a/core/src/main/java/hudson/model/queue/WorkUnitContext.java +++ b/core/src/main/java/hudson/model/queue/WorkUnitContext.java @@ -23,14 +23,18 @@ */ package hudson.model.queue; +import hudson.ExtensionList; import hudson.model.Action; import hudson.model.Executor; +import hudson.model.ExecutorListener; import hudson.model.Queue; import hudson.model.Queue.BuildableItem; import hudson.model.Queue.Task; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; @@ -41,6 +45,8 @@ */ public final class WorkUnitContext { + private static final Logger LOGGER = Logger.getLogger(WorkUnitContext.class.getName()); + public final BuildableItem item; public final Task task; @@ -81,6 +87,13 @@ protected void onCriteriaMet() { Executor e = Executor.currentExecutor(); if (e.getCurrentWorkUnit().isMainWork()) { e.getOwner().taskAccepted(e,task); + for (ExecutorListener listener : ExtensionList.lookup(ExecutorListener.class)) { + try { + listener.taskAccepted(e, task); + } catch (RuntimeException x) { + LOGGER.log(Level.WARNING, null, x); + } + } } } }; @@ -120,6 +133,13 @@ public void synchronizeStart() throws InterruptedException { WorkUnit wu = e.getCurrentWorkUnit(); if (wu.isMainWork()) { future.start.set(e.getCurrentExecutable()); + for (ExecutorListener listener : ExtensionList.lookup(ExecutorListener.class)) { + try { + listener.taskStarted(e, task); + } catch (RuntimeException x) { + LOGGER.log(Level.WARNING, null, x); + } + } } } } @@ -149,9 +169,23 @@ public void synchronizeEnd(Executor e, Queue.Executable executable, Throwable pr if (problems == null) { future.set(executable); e.getOwner().taskCompleted(e, task, duration); + for (ExecutorListener listener : ExtensionList.lookup(ExecutorListener.class)) { + try { + listener.taskCompleted(e, task, duration); + } catch (RuntimeException x) { + LOGGER.log(Level.WARNING, null, x); + } + } } else { future.set(problems); e.getOwner().taskCompletedWithProblems(e, task, duration, problems); + for (ExecutorListener listener : ExtensionList.lookup(ExecutorListener.class)) { + try { + listener.taskCompletedWithProblems(e, task, duration, problems); + } catch (RuntimeException x) { + LOGGER.log(Level.WARNING, null, x); + } + } } } } diff --git a/core/src/main/java/hudson/slaves/SlaveComputer.java b/core/src/main/java/hudson/slaves/SlaveComputer.java index f57077b26c14..72c564fea656 100644 --- a/core/src/main/java/hudson/slaves/SlaveComputer.java +++ b/core/src/main/java/hudson/slaves/SlaveComputer.java @@ -325,7 +325,6 @@ protected Future _connect(boolean forceReconnect) { @Override public void taskAccepted(Executor executor, Queue.Task task) { - super.taskAccepted(executor, task); if (launcher instanceof ExecutorListener) { ((ExecutorListener)launcher).taskAccepted(executor, task); } @@ -337,9 +336,19 @@ public void taskAccepted(Executor executor, Queue.Task task) { } } + @Override + public void taskStarted(Executor executor, Queue.Task task) { + if (launcher instanceof ExecutorListener) { + ((ExecutorListener)launcher).taskStarted(executor, task); + } + RetentionStrategy r = getRetentionStrategy(); + if (r instanceof ExecutorListener) { + ((ExecutorListener) r).taskStarted(executor, task); + } + } + @Override public void taskCompleted(Executor executor, Queue.Task task, long durationMS) { - super.taskCompleted(executor, task, durationMS); if (launcher instanceof ExecutorListener) { ((ExecutorListener)launcher).taskCompleted(executor, task, durationMS); } @@ -351,7 +360,6 @@ public void taskCompleted(Executor executor, Queue.Task task, long durationMS) { @Override public void taskCompletedWithProblems(Executor executor, Queue.Task task, long durationMS, Throwable problems) { - super.taskCompletedWithProblems(executor, task, durationMS, problems); if (launcher instanceof ExecutorListener) { ((ExecutorListener)launcher).taskCompletedWithProblems(executor, task, durationMS, problems); } From d1f4c61cf8b1e9e4fa24328dd0330a854846636a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szalay=20Krist=C3=B3f?= <32012862+littletof@users.noreply.github.com> Date: Mon, 25 Oct 2021 23:05:38 +0200 Subject: [PATCH 11/44] [JENKINS-66728] Un-inline UpdateCenter/index.jelly (CSP issue) #Hacktoberfest (#5848) Co-authored-by: Wadeck Follonier --- .../hudson/model/UpdateCenter/index.jelly | 43 +------------------ .../model/UpdateCenter/update-center.js | 38 ++++++++++++++++ 2 files changed, 39 insertions(+), 42 deletions(-) create mode 100644 core/src/main/resources/hudson/model/UpdateCenter/update-center.js diff --git a/core/src/main/resources/hudson/model/UpdateCenter/index.jelly b/core/src/main/resources/hudson/model/UpdateCenter/index.jelly index ae6bbc924005..8e8209cdafba 100644 --- a/core/src/main/resources/hudson/model/UpdateCenter/index.jelly +++ b/core/src/main/resources/hudson/model/UpdateCenter/index.jelly @@ -29,52 +29,11 @@ THE SOFTWARE. +

${%Installing Plugins/Upgrades}

- - - diff --git a/core/src/main/resources/hudson/model/UpdateCenter/update-center.js b/core/src/main/resources/hudson/model/UpdateCenter/update-center.js new file mode 100644 index 000000000000..acb0e8ee559a --- /dev/null +++ b/core/src/main/resources/hudson/model/UpdateCenter/update-center.js @@ -0,0 +1,38 @@ +function submitScheduleForm(el) { + var form = document.getElementById("scheduleRestart"); + form.action = el.checked ? "safeRestart" : "cancelRestart"; + crumb.appendToForm(form); + form.submit(); +} + +function refresh() { + window.setTimeout(function() { + new Ajax.Request("./body", { + onSuccess: function(rsp) { + var div = document.createElement('div'); + div.innerHTML = rsp.responseText; + + var rows = div.children[0].rows; + for(var i=0; i Date: Mon, 25 Oct 2021 23:06:07 +0200 Subject: [PATCH 12/44] [JENKINS-66968] Fix scaling of buttons in security realm (#5849) --- .../hudson/security/HudsonPrivateSecurityRealm/index.jelly | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/index.jelly b/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/index.jelly index 9f1ab4374ef3..b9b30db7e437 100644 --- a/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/index.jelly +++ b/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/index.jelly @@ -43,7 +43,7 @@ THE SOFTWARE. ${user.id} ${user} - + From 1b674e78c41ea4fd6a9ce8d5b1d25994b8210cf4 Mon Sep 17 00:00:00 2001 From: Mark Waite Date: Mon, 25 Oct 2021 22:27:35 -0600 Subject: [PATCH 13/44] Fix javadoc warning --- core/src/main/java/hudson/model/ExecutorListener.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/src/main/java/hudson/model/ExecutorListener.java b/core/src/main/java/hudson/model/ExecutorListener.java index 754c32976739..c0e5e6590bed 100644 --- a/core/src/main/java/hudson/model/ExecutorListener.java +++ b/core/src/main/java/hudson/model/ExecutorListener.java @@ -46,10 +46,9 @@ default void taskAccepted(Executor executor, Queue.Task task) {} * Called whenever a task is started by an executor. * @param executor The executor. * @param task The task. - * @param executable the executable. * @since TODO */ - default void taskStarted(Executor e, Queue.Task task) {} + default void taskStarted(Executor executor, Queue.Task task) {} /** * Called whenever a task is completed without any problems by an executor. From f71615b516398658c9c8ea61babeb10ad3d5ee71 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Oct 2021 08:12:14 +0100 Subject: [PATCH 14/44] Bump jenkins-test-harness from 1626.v46b0925e70db to 1640.vf14ba9a1eb71 (#5854) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- test/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/pom.xml b/test/pom.xml index 45e69b6b0942..bf13e2e0b20b 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -81,7 +81,7 @@ THE SOFTWARE. ${project.groupId} jenkins-test-harness - 1626.v46b0925e70db + 1640.vf14ba9a1eb71 test From b6558625919c3d53e933416f88cbc209b4f19c24 Mon Sep 17 00:00:00 2001 From: Jenkins Release Bot <66998184+jenkins-release-bot@users.noreply.github.com> Date: Tue, 26 Oct 2021 12:31:00 +0000 Subject: [PATCH 15/44] [maven-release-plugin] prepare release jenkins-2.318 --- bom/pom.xml | 2 +- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 4 ++-- test/pom.xml | 2 +- war/pom.xml | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bom/pom.xml b/bom/pom.xml index 2e356c35918e..ddb1ab7af138 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main jenkins-parent - ${revision}${changelist} + 2.318 jenkins-bom diff --git a/cli/pom.xml b/cli/pom.xml index 3838594a564f..bbb5ea5eaca7 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main jenkins-parent - ${revision}${changelist} + 2.318 cli diff --git a/core/pom.xml b/core/pom.xml index 694d0b9695d7..e2e7659350c6 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main jenkins-parent - ${revision}${changelist} + 2.318 jenkins-core diff --git a/pom.xml b/pom.xml index 4336e05f5b60..29570a0f9365 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main jenkins-parent - ${revision}${changelist} + 2.318 pom Jenkins main module @@ -61,7 +61,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - ${scmTag} + jenkins-2.318 diff --git a/test/pom.xml b/test/pom.xml index bf13e2e0b20b..f8ae97f8645d 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main jenkins-parent - ${revision}${changelist} + 2.318 jenkins-test diff --git a/war/pom.xml b/war/pom.xml index 53ec00960c0c..3a19d2b1ad6f 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main jenkins-parent - ${revision}${changelist} + 2.318 jenkins-war From 999f1b838db34677b7447e876ec554b9b4c122de Mon Sep 17 00:00:00 2001 From: Jenkins Release Bot <66998184+jenkins-release-bot@users.noreply.github.com> Date: Tue, 26 Oct 2021 12:31:20 +0000 Subject: [PATCH 16/44] [maven-release-plugin] prepare for next development iteration --- bom/pom.xml | 2 +- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 6 +++--- test/pom.xml | 2 +- war/pom.xml | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bom/pom.xml b/bom/pom.xml index ddb1ab7af138..2e356c35918e 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main jenkins-parent - 2.318 + ${revision}${changelist} jenkins-bom diff --git a/cli/pom.xml b/cli/pom.xml index bbb5ea5eaca7..3838594a564f 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main jenkins-parent - 2.318 + ${revision}${changelist} cli diff --git a/core/pom.xml b/core/pom.xml index e2e7659350c6..694d0b9695d7 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main jenkins-parent - 2.318 + ${revision}${changelist} jenkins-core diff --git a/pom.xml b/pom.xml index 29570a0f9365..6330c103fca2 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main jenkins-parent - 2.318 + ${revision}${changelist} pom Jenkins main module @@ -61,7 +61,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - jenkins-2.318 + ${scmTag} @@ -70,7 +70,7 @@ THE SOFTWARE. - 2.318 + 2.319 -SNAPSHOT - com.google.guava - guava - @@ -538,9 +534,17 @@ THE SOFTWARE. com.google.guava guava - - com.google.code.findbugs - jsr305 + + com.google.errorprone + error_prone_annotations + + + com.google.j2objc + j2objc-annotations + + + org.checkerframework + checker-qual diff --git a/test/pom.xml b/test/pom.xml index 9735ce023daf..806fc921deb5 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -119,12 +119,6 @@ THE SOFTWARE. antisamy-markup-formatter 2.4 test - - - com.google.guava - guava - - org.jenkins-ci.plugins From 1d0e81acc820ea6531b33eac3a630dd10700e16a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Nov 2021 20:31:20 +0000 Subject: [PATCH 23/44] Bump checkstyle from 9.0.1 to 9.1 (#5873) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ec6f0d66de19..4648af9415ca 100644 --- a/pom.xml +++ b/pom.xml @@ -418,7 +418,7 @@ THE SOFTWARE. com.puppycrawl.tools checkstyle - 9.0.1 + 9.1 From 104c751d907919dd53f5090f84d53c671a66457b Mon Sep 17 00:00:00 2001 From: Daniel Beck Date: Tue, 2 Nov 2021 03:46:28 +0000 Subject: [PATCH 24/44] [SECURITY-2455] --- core/src/main/java/hudson/FilePath.java | 19 ------------------ .../jenkins/security/Security2455Test.java | 17 ++++++++++++++++ .../testRemoteLocalUnzip/file.zip | Bin 0 -> 478 bytes 3 files changed, 17 insertions(+), 19 deletions(-) create mode 100644 test/src/test/resources/jenkins/security/Security2455Test/testRemoteLocalUnzip/file.zip diff --git a/core/src/main/java/hudson/FilePath.java b/core/src/main/java/hudson/FilePath.java index 3245ae63c049..9ecbe010751b 100644 --- a/core/src/main/java/hudson/FilePath.java +++ b/core/src/main/java/hudson/FilePath.java @@ -215,11 +215,6 @@ public final class FilePath implements SerializableOnlyOverRemoting { */ private static final int MAX_REDIRECTS = 20; - /** - * Escape hatch for some additional protections against sending callables intended to be locally used only - */ - private static /* non-final for Groovy */ boolean REJECT_LOCAL_CALLABLE_DESERIALIZATION = SystemProperties.getBoolean(FilePath.class.getName() + ".rejectLocalCallableDeserialization", true); - /** * When this {@link FilePath} represents the remote path, * this field is always non-null on the controller (the field represents @@ -601,13 +596,6 @@ public Void invoke(File dir, VirtualChannel channel) throws IOException, Interru return null; } private static final long serialVersionUID = 1L; - - protected Object readResolve() { - if (REJECT_LOCAL_CALLABLE_DESERIALIZATION) { - throw new IllegalStateException("This callable is not intended to be sent through a channel"); - } - return this; - } } /** @@ -660,13 +648,6 @@ public Void invoke(File dir, VirtualChannel channel) throws IOException, Interru return null; } private static final long serialVersionUID = 1L; - - protected Object readResolve() { - if (REJECT_LOCAL_CALLABLE_DESERIALIZATION) { - throw new IllegalStateException("This callable is not intended to be sent through a channel"); - } - return this; - } } /** diff --git a/test/src/test/java/jenkins/security/Security2455Test.java b/test/src/test/java/jenkins/security/Security2455Test.java index e190b033bffc..71613ceb60d3 100644 --- a/test/src/test/java/jenkins/security/Security2455Test.java +++ b/test/src/test/java/jenkins/security/Security2455Test.java @@ -21,6 +21,7 @@ import hudson.model.Node; import hudson.model.TaskListener; import hudson.remoting.VirtualChannel; +import hudson.slaves.DumbSlave; import java.io.File; import java.io.FileReader; import java.io.IOException; @@ -809,6 +810,22 @@ public Object call() throws Exception { // -------- + // Misc tests + + @LocalData + @Test + public void testRemoteLocalUnzip() throws Exception { + final DumbSlave onlineSlave = j.createOnlineSlave(); + final File zipFile = new File(j.jenkins.getRootDir(), "file.zip"); + assertTrue(zipFile.isFile()); + final FilePath agentRootPath = onlineSlave.getRootPath(); + final FilePath agentZipPath = agentRootPath.child("file.zip"); + new FilePath(zipFile).copyTo(agentZipPath); + agentZipPath.unzip(agentRootPath); + } + + // -------- + // Utility functions protected static FilePath toFilePathOnController(File file) { diff --git a/test/src/test/resources/jenkins/security/Security2455Test/testRemoteLocalUnzip/file.zip b/test/src/test/resources/jenkins/security/Security2455Test/testRemoteLocalUnzip/file.zip new file mode 100644 index 0000000000000000000000000000000000000000..619f162bbc42eb5a88ecaa601d8a913b3d5dee14 GIT binary patch literal 478 zcmWIWW@Zs#-~hr;*1o|ENPwL|fgvqFU$3O1Bs7GVf!#7cEfIt>fVi}Rn}Lz#D|A62_JlY z!aneabp$jpO^{$#7m#Kwloe?HAuZ9t)TgNE7@+2HoJm2-z;v&3=dXI}Xq@o8 zdfHP*PgB>=*V9wSGn9{mP1EO;zcAkOZIGTXnzIB(NM-0$)AQuLBGct)VqXr7h xU!XvNfhCO~7B&xH2_keuku3xT2@EW03 Date: Tue, 2 Nov 2021 18:27:39 +0000 Subject: [PATCH 25/44] [maven-release-plugin] prepare release jenkins-2.319 --- bom/pom.xml | 2 +- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 4 ++-- test/pom.xml | 2 +- war/pom.xml | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bom/pom.xml b/bom/pom.xml index 2e356c35918e..3bcd3d5e0c04 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main jenkins-parent - ${revision}${changelist} + 2.319 jenkins-bom diff --git a/cli/pom.xml b/cli/pom.xml index 3838594a564f..7502129136e3 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main jenkins-parent - ${revision}${changelist} + 2.319 cli diff --git a/core/pom.xml b/core/pom.xml index 694d0b9695d7..4c26fc6b39cf 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main jenkins-parent - ${revision}${changelist} + 2.319 jenkins-core diff --git a/pom.xml b/pom.xml index 1901784813ba..d454bdbddf8b 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main jenkins-parent - ${revision}${changelist} + 2.319 pom Jenkins main module @@ -61,7 +61,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - ${scmTag} + jenkins-2.319 diff --git a/test/pom.xml b/test/pom.xml index bf13e2e0b20b..3bc8a54a0139 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main jenkins-parent - ${revision}${changelist} + 2.319 jenkins-test diff --git a/war/pom.xml b/war/pom.xml index 53ec00960c0c..113cb3d225f5 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main jenkins-parent - ${revision}${changelist} + 2.319 jenkins-war From ef3bd2e83942ce4652511d3451fad56c0f61046b Mon Sep 17 00:00:00 2001 From: Jenkins Release Bot <66998184+jenkins-release-bot@users.noreply.github.com> Date: Tue, 2 Nov 2021 18:28:02 +0000 Subject: [PATCH 26/44] [maven-release-plugin] prepare for next development iteration --- bom/pom.xml | 2 +- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 6 +++--- test/pom.xml | 2 +- war/pom.xml | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bom/pom.xml b/bom/pom.xml index 3bcd3d5e0c04..2e356c35918e 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main jenkins-parent - 2.319 + ${revision}${changelist} jenkins-bom diff --git a/cli/pom.xml b/cli/pom.xml index 7502129136e3..3838594a564f 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main jenkins-parent - 2.319 + ${revision}${changelist} cli diff --git a/core/pom.xml b/core/pom.xml index 4c26fc6b39cf..694d0b9695d7 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main jenkins-parent - 2.319 + ${revision}${changelist} jenkins-core diff --git a/pom.xml b/pom.xml index d454bdbddf8b..74445dee26a1 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ THE SOFTWARE. org.jenkins-ci.main jenkins-parent - 2.319 + ${revision}${changelist} pom Jenkins main module @@ -61,7 +61,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - jenkins-2.319 + ${scmTag} @@ -70,7 +70,7 @@ THE SOFTWARE. - 2.319 + 2.320 -SNAPSHOT - + - - diff --git a/core/src/main/resources/jenkins/model/Jenkins/newView.properties b/core/src/main/resources/jenkins/model/Jenkins/newView.properties new file mode 100644 index 000000000000..499c94c6edaf --- /dev/null +++ b/core/src/main/resources/jenkins/model/Jenkins/newView.properties @@ -0,0 +1,2 @@ +New\ View=New view +View\ name=Name \ No newline at end of file diff --git a/core/src/main/resources/lib/form/advanced.jelly b/core/src/main/resources/lib/form/advanced.jelly index be1951308958..1e59572b3e48 100644 --- a/core/src/main/resources/lib/form/advanced.jelly +++ b/core/src/main/resources/lib/form/advanced.jelly @@ -40,8 +40,8 @@ THE SOFTWARE. -
- +
+ diff --git a/core/src/main/resources/hudson/security/csrf/GlobalCrumbIssuerConfiguration/help-csrf_it.html b/core/src/main/resources/hudson/security/csrf/GlobalCrumbIssuerConfiguration/help-csrf_it.html index 5d434891f23e..609b059c9aec 100644 --- a/core/src/main/resources/hudson/security/csrf/GlobalCrumbIssuerConfiguration/help-csrf_it.html +++ b/core/src/main/resources/hudson/security/csrf/GlobalCrumbIssuerConfiguration/help-csrf_it.html @@ -23,5 +23,5 @@

È possibile reperire ulteriori informazioni sugli exploit CSRF - qui. + qui.

diff --git a/core/src/main/resources/hudson/security/csrf/GlobalCrumbIssuerConfiguration/help-csrf_ja.html b/core/src/main/resources/hudson/security/csrf/GlobalCrumbIssuerConfiguration/help-csrf_ja.html index 11a67adb694c..d79200a977a8 100644 --- a/core/src/main/resources/hudson/security/csrf/GlobalCrumbIssuerConfiguration/help-csrf_ja.html +++ b/core/src/main/resources/hudson/security/csrf/GlobalCrumbIssuerConfiguration/help-csrf_ja.html @@ -5,5 +5,5 @@ このオプションを有効にすると、Jenkinsサーバでの変更を伴うリクエストについて、ノンス(使い捨ての乱数)や"crumb"をチェックします。 フォームの実行やリモートAPIの呼び出しでも同様です。

- CSRFについての詳細は、ここを参照してください。 + CSRFについての詳細は、ここを参照してください。

diff --git a/core/src/main/resources/hudson/security/csrf/GlobalCrumbIssuerConfiguration/help-csrf_zh_TW.html b/core/src/main/resources/hudson/security/csrf/GlobalCrumbIssuerConfiguration/help-csrf_zh_TW.html index c43a29127b83..8c0851877f9a 100644 --- a/core/src/main/resources/hudson/security/csrf/GlobalCrumbIssuerConfiguration/help-csrf_zh_TW.html +++ b/core/src/main/resources/hudson/security/csrf/GlobalCrumbIssuerConfiguration/help-csrf_zh_TW.html @@ -5,5 +5,5 @@ 啟動這個選項後,Jenkins 會檢查任何可能修改 Jenkins 伺服器設定的要求,看看產生的 Nonce 值 (或叫做 "Crumb") 是否正確。 這類要求包含表單送出,以及呼叫遠端 API。

- 您可以在這裡找到更多有關 CSRF 入侵的資訊。 + 您可以在這裡找到更多有關 CSRF 入侵的資訊。 diff --git a/core/src/main/resources/hudson/slaves/JNLPLauncher/help_es.properties b/core/src/main/resources/hudson/slaves/JNLPLauncher/help_es.properties index 26aee9fd3a1f..0f7743712438 100644 --- a/core/src/main/resources/hudson/slaves/JNLPLauncher/help_es.properties +++ b/core/src/main/resources/hudson/slaves/JNLPLauncher/help_es.properties @@ -20,7 +20,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -blurb=Arranca un agente haciendo uso de JNLP.\ +blurb=Arranca un agente haciendo uso de JNLP.\ De modo que el agente inicia la ejecucin, por lo que los agentes no necesitan una IP accesible desde el master. \ Incluso es posible arrancar una ejecucin sin GUI, como puede ser un servicio Windows. diff --git a/core/src/main/resources/hudson/slaves/SlaveComputer/jenkins-agent.jnlp.jelly b/core/src/main/resources/hudson/slaves/SlaveComputer/jenkins-agent.jnlp.jelly index fc2ed92676bb..22b7a562448e 100644 --- a/core/src/main/resources/hudson/slaves/SlaveComputer/jenkins-agent.jnlp.jelly +++ b/core/src/main/resources/hudson/slaves/SlaveComputer/jenkins-agent.jnlp.jelly @@ -28,7 +28,7 @@ THE SOFTWARE. diff --git a/core/src/main/resources/hudson/tools/ZipExtractionInstaller/help.html b/core/src/main/resources/hudson/tools/ZipExtractionInstaller/help.html index de90c2eaa1d8..d36d72d8c181 100644 --- a/core/src/main/resources/hudson/tools/ZipExtractionInstaller/help.html +++ b/core/src/main/resources/hudson/tools/ZipExtractionInstaller/help.html @@ -1,6 +1,5 @@

Downloads a tool archive and installs it within Jenkins's working directory. - Example: http://apache.promopeddler.com/ant/binaries/apache-ant-1.7.1-bin.zip - (or whatever mirror is closest to your server) - and specify a subdir of apache-ant-1.7.1. + Example: https://downloads.apache.org/ant/binaries/apache-ant-1.10.12-bin.zip + and specify a subdir of apache-ant-1.10.12.
diff --git a/core/src/main/resources/hudson/tools/ZipExtractionInstaller/help_bg.html b/core/src/main/resources/hudson/tools/ZipExtractionInstaller/help_bg.html index 7e10b81bca44..5885e35c97fe 100644 --- a/core/src/main/resources/hudson/tools/ZipExtractionInstaller/help_bg.html +++ b/core/src/main/resources/hudson/tools/ZipExtractionInstaller/help_bg.html @@ -1,6 +1,5 @@ -
Изтегляне на архив с програма и инсталирането ѝ в работната директория на Jenkins. - Примерно: http://apache.promopeddler.com/ant/binaries/apache-ant-1.7.1-bin.zip - (или друг сървър-огледало, който е по-близо или бърз) и укажете поддиректория на - apache-ant-1.7.1. + Примерно: https://downloads.apache.org/ant/binaries/apache-ant-1.10.12-bin.zip + и укажете поддиректория на apache-ant-1.10.12.
diff --git a/core/src/main/resources/hudson/tools/ZipExtractionInstaller/help_de.html b/core/src/main/resources/hudson/tools/ZipExtractionInstaller/help_de.html index 164576bdcc9a..3b0c19163a28 100644 --- a/core/src/main/resources/hudson/tools/ZipExtractionInstaller/help_de.html +++ b/core/src/main/resources/hudson/tools/ZipExtractionInstaller/help_de.html @@ -1,7 +1,6 @@
Lädt ein Hilfsprogramm als gepacktes Archiv herunter und installiert es innerhalb des Jenkins-Arbeitsverzeichnisses. - Beispiel: http://apache.promopeddler.com/ant/binaries/apache-ant-1.7.1-bin.zip - (oder ein Spiegelserver, der näher zu Ihrem Jenkins-Server liegt) mit der Angabe eines - Unterverzeichnisses von apache-ant-1.7.1. + Beispiel: https://downloads.apache.org/ant/binaries/apache-ant-1.10.12-bin.zip + mit der Angabe eines Unterverzeichnisses von apache-ant-1.10.12.
diff --git a/core/src/main/resources/hudson/tools/ZipExtractionInstaller/help_it.html b/core/src/main/resources/hudson/tools/ZipExtractionInstaller/help_it.html index 727280351212..f55d1370d4c4 100644 --- a/core/src/main/resources/hudson/tools/ZipExtractionInstaller/help_it.html +++ b/core/src/main/resources/hudson/tools/ZipExtractionInstaller/help_it.html @@ -1,7 +1,6 @@
Scarica un archivio contenente uno strumento e lo installa all'interno della directory di lavoro di Jenkins. Ad esempio: - http://apache.promopeddler.com/ant/binaries/apache-ant-1.7.1-bin.zip - (o il mirror più vicino al proprio server) e si specifichi una - sottodirectory di apache-ant-1.7.1. + https://downloads.apache.org/ant/binaries/apache-ant-1.10.12-bin.zip + e si specifichi una sottodirectory di apache-ant-1.10.12.
diff --git a/core/src/main/resources/hudson/tools/ZipExtractionInstaller/help_ja.html b/core/src/main/resources/hudson/tools/ZipExtractionInstaller/help_ja.html index abf95e5f7009..9b80732de1f2 100644 --- a/core/src/main/resources/hudson/tools/ZipExtractionInstaller/help_ja.html +++ b/core/src/main/resources/hudson/tools/ZipExtractionInstaller/help_ja.html @@ -1,6 +1,5 @@
ツールのアーカイブをダウンロードして、Jenkinsのワークディレクトリにインストールします。 - 例えば: http://apache.promopeddler.com/ant/binaries/apache-ant-1.7.1-bin.zip - (もしくは、あなたのサーバーに一番近いミラーサイト) - そして、サブディレクトリにapache-ant-1.7.1を指定します。 + 例えば: https://downloads.apache.org/ant/binaries/apache-ant-1.10.12-bin.zip + そして、サブディレクトリにapache-ant-1.10.12を指定します。
diff --git a/core/src/main/resources/hudson/tools/ZipExtractionInstaller/help_zh_TW.html b/core/src/main/resources/hudson/tools/ZipExtractionInstaller/help_zh_TW.html index 0844a8985639..95af127631f2 100644 --- a/core/src/main/resources/hudson/tools/ZipExtractionInstaller/help_zh_TW.html +++ b/core/src/main/resources/hudson/tools/ZipExtractionInstaller/help_zh_TW.html @@ -1,5 +1,5 @@
下載工具壓縮檔,並安裝到 Jenkins 工作目錄中。 - 例如: http://apache.promopeddler.com/ant/binaries/apache-ant-1.7.1-bin.zip - (或是其他離您伺服器進的鏡像站) 並指定子目錄 apache-ant-1.7.1。 + 例如: https://downloads.apache.org/ant/binaries/apache-ant-1.10.12-bin.zip + 並指定子目錄 apache-ant-1.10.12
diff --git a/core/src/main/resources/hudson/triggers/TimerTrigger/help.html b/core/src/main/resources/hudson/triggers/TimerTrigger/help.html index 581cdbf13f03..03fa53f40e59 100644 --- a/core/src/main/resources/hudson/triggers/TimerTrigger/help.html +++ b/core/src/main/resources/hudson/triggers/TimerTrigger/help.html @@ -1,5 +1,5 @@
- Provides a cron-like feature + Provides a cron-like feature to periodically execute this project.

diff --git a/core/src/main/resources/hudson/triggers/TimerTrigger/help_bg.html b/core/src/main/resources/hudson/triggers/TimerTrigger/help_bg.html index c99673fe5877..f28073ae3f4e 100644 --- a/core/src/main/resources/hudson/triggers/TimerTrigger/help_bg.html +++ b/core/src/main/resources/hudson/triggers/TimerTrigger/help_bg.html @@ -1,6 +1,6 @@

Възможност за периодично изпълнение на този проект на база време, - подобно но програмата cron. + подобно но програмата cron.

Това дава възможност да ползвате Jenkins като заместител на cron. diff --git a/core/src/main/resources/hudson/triggers/TimerTrigger/help_de.html b/core/src/main/resources/hudson/triggers/TimerTrigger/help_de.html index 80c0643b1a23..c4ba27215850 100644 --- a/core/src/main/resources/hudson/triggers/TimerTrigger/help_de.html +++ b/core/src/main/resources/hudson/triggers/TimerTrigger/help_de.html @@ -1,6 +1,6 @@

Erlaubt eine Ausführung des Projekts in regelmäßigen Zeitintervallen, ähnlich - dem Cron-Befehl. + dem Cron-Befehl.

Dieses Merkmal ist hauptsächlich als Cron-Ersatz gedacht und ist diff --git a/core/src/main/resources/hudson/triggers/TimerTrigger/help_fr.html b/core/src/main/resources/hudson/triggers/TimerTrigger/help_fr.html index 94dcf17d1690..539776a666ea 100644 --- a/core/src/main/resources/hudson/triggers/TimerTrigger/help_fr.html +++ b/core/src/main/resources/hudson/triggers/TimerTrigger/help_fr.html @@ -1,6 +1,6 @@ 

Fournit une fonctionnalité de type - cron + cron afin d'exécuter le projet périodiquement.

diff --git a/core/src/main/resources/hudson/triggers/TimerTrigger/help_it.html b/core/src/main/resources/hudson/triggers/TimerTrigger/help_it.html index 3f4c89b01eac..9e9b46f4028e 100644 --- a/core/src/main/resources/hudson/triggers/TimerTrigger/help_it.html +++ b/core/src/main/resources/hudson/triggers/TimerTrigger/help_it.html @@ -1,5 +1,5 @@

- Fornisce una funzionalità simile a cron + Fornisce una funzionalità simile a cron per eseguire periodicamente questo progetto.

diff --git a/core/src/main/resources/hudson/triggers/TimerTrigger/help_ja.html b/core/src/main/resources/hudson/triggers/TimerTrigger/help_ja.html index f3e83b9e65d8..7d117cf6abba 100644 --- a/core/src/main/resources/hudson/triggers/TimerTrigger/help_ja.html +++ b/core/src/main/resources/hudson/triggers/TimerTrigger/help_ja.html @@ -1,5 +1,5 @@

- このプロジェクトを定期的に実行するクーロンのような機能を提供します。 + このプロジェクトを定期的に実行するクーロンのような機能を提供します。

この機能は、クーロンの代替品としてJenkinsを使用するのが第一なので、 diff --git a/core/src/main/resources/hudson/triggers/TimerTrigger/help_pt_BR.html b/core/src/main/resources/hudson/triggers/TimerTrigger/help_pt_BR.html index 4f4b8a33be80..23ea6fb2d525 100644 --- a/core/src/main/resources/hudson/triggers/TimerTrigger/help_pt_BR.html +++ b/core/src/main/resources/hudson/triggers/TimerTrigger/help_pt_BR.html @@ -1,5 +1,5 @@

- Fornece uma funcionalidade ao estilo do cron + Fornece uma funcionalidade ao estilo do cron para periodicamente executar este projeto.

diff --git a/core/src/main/resources/hudson/triggers/TimerTrigger/help_ru.html b/core/src/main/resources/hudson/triggers/TimerTrigger/help_ru.html index 51deed16c722..3a3a32bdf711 100644 --- a/core/src/main/resources/hudson/triggers/TimerTrigger/help_ru.html +++ b/core/src/main/resources/hudson/triggers/TimerTrigger/help_ru.html @@ -1,5 +1,5 @@

- �।��⠢��� cron-like �㭪樮���쭮��� + �।��⠢��� cron-like �㭪樮���쭮��� ��� 㪠����� �ᯨᠭ�� ᡮப �஥��.

diff --git a/core/src/main/resources/hudson/triggers/TimerTrigger/help_tr.html b/core/src/main/resources/hudson/triggers/TimerTrigger/help_tr.html index d3fea16b9d8d..00193471c65f 100644 --- a/core/src/main/resources/hudson/triggers/TimerTrigger/help_tr.html +++ b/core/src/main/resources/hudson/triggers/TimerTrigger/help_tr.html @@ -1,5 +1,5 @@

- Bu özellik, projeyi periyodik olarak cron + Bu özellik, projeyi periyodik olarak cron gibi çalıştırabilmeyi sağlar.

diff --git a/core/src/main/resources/hudson/triggers/TimerTrigger/help_zh_TW.html b/core/src/main/resources/hudson/triggers/TimerTrigger/help_zh_TW.html index cf10132ea482..11dc9ee2fe34 100644 --- a/core/src/main/resources/hudson/triggers/TimerTrigger/help_zh_TW.html +++ b/core/src/main/resources/hudson/triggers/TimerTrigger/help_zh_TW.html @@ -1,5 +1,5 @@

- 提供類似 cron 的功能,定期執行專案。 + 提供類似 cron 的功能,定期執行專案。

這個功能主要是讓 Jenkins 取代 cron,而且持續建置軟體專案並不是理想選項。 diff --git a/core/src/main/resources/jenkins/model/Jenkins/help-markupFormatter.html b/core/src/main/resources/jenkins/model/Jenkins/help-markupFormatter.html index 21f0e1d8ef39..12621e2aa042 100644 --- a/core/src/main/resources/jenkins/model/Jenkins/help-markupFormatter.html +++ b/core/src/main/resources/jenkins/model/Jenkins/help-markupFormatter.html @@ -8,6 +8,6 @@

While this is convenient and people often use it to load <iframe>, <script>. and so on to mash up data from other sources, this capability enables malicious users to mount - XSS attacks. + XSS attacks. If the risk outweighs the benefit, install additional markup formatter plugins and use them.

diff --git a/core/src/main/resources/jenkins/model/Jenkins/help-markupFormatter_bg.html b/core/src/main/resources/jenkins/model/Jenkins/help-markupFormatter_bg.html index ed0ba000e1f4..e0cd7e418e2f 100644 --- a/core/src/main/resources/jenkins/model/Jenkins/help-markupFormatter_bg.html +++ b/core/src/main/resources/jenkins/model/Jenkins/help-markupFormatter_bg.html @@ -10,7 +10,7 @@ Това е доста удобно и хората го ползват, за да зареждат <iframe>, <script> и т.н., това позволява на недобронамерените потребители да извършат атаки чрез - XSS. + XSS. Ако рискът е прекомерно голям, инсталирайте допълнителна приставка за форматиране на текста и ползвайте нея.
diff --git a/core/src/main/resources/jenkins/model/Jenkins/help-markupFormatter_ja.html b/core/src/main/resources/jenkins/model/Jenkins/help-markupFormatter_ja.html index 380b735d1de3..8eca80f7610d 100644 --- a/core/src/main/resources/jenkins/model/Jenkins/help-markupFormatter_ja.html +++ b/core/src/main/resources/jenkins/model/Jenkins/help-markupFormatter_ja.html @@ -6,7 +6,7 @@

これはとても便利なので、<iframe>, <script>をロードするために、また他のソースからのデータを取り込むためによく使用しますが、 - 悪意のあるユーザーがクロスサイトスクリプティング + 悪意のあるユーザーがクロスサイトスクリプティング をしかけることを容易にしてしまいます。 便利さより危険性を重視するなら、他のフォーマッタープラグインをインストールして使用してください。 -

\ No newline at end of file +
diff --git a/core/src/main/resources/jenkins/model/Jenkins/help-markupFormatter_zh_TW.html b/core/src/main/resources/jenkins/model/Jenkins/help-markupFormatter_zh_TW.html index 01da0749a14c..8753cb6fb4a3 100644 --- a/core/src/main/resources/jenkins/model/Jenkins/help-markupFormatter_zh_TW.html +++ b/core/src/main/resources/jenkins/model/Jenkins/help-markupFormatter_zh_TW.html @@ -7,6 +7,6 @@

這樣很方便,大家常用來載入 <iframe> 或 <script>,整合其他來源的資料。 但是也有可能被惡意使用者掛上 - XSS 攻擊。 + XSS 攻擊。 如果您評估的風險大過好處,請另外安裝使用標記格式外掛程式。

diff --git a/core/src/main/resources/lib/form/textarea.jelly b/core/src/main/resources/lib/form/textarea.jelly index ab7df226e634..98666837631c 100644 --- a/core/src/main/resources/lib/form/textarea.jelly +++ b/core/src/main/resources/lib/form/textarea.jelly @@ -63,7 +63,7 @@ THE SOFTWARE. Turns this text area into CodeMirror-assisted code editing text area. This attribute specifies the mode of CodeMirror, such as "text/x-java". - See http://codemirror.net/ for more details. + See https://codemirror.net/ for more details. Specifies additional key/value pairs in the JSON format (except the start and end bracket) diff --git a/core/src/main/resources/lib/form/validateButton.jelly b/core/src/main/resources/lib/form/validateButton.jelly index d236f2c512fb..21c84e738c48 100644 --- a/core/src/main/resources/lib/form/validateButton.jelly +++ b/core/src/main/resources/lib/form/validateButton.jelly @@ -29,7 +29,7 @@ THE SOFTWARE. See - http://wiki.jenkins-ci.org/display/JENKINS/Jelly+form+controls + https://www.jenkins.io/doc/developer/forms/jelly-form-controls/ for the reference. diff --git a/core/src/main/resources/lib/hudson/scriptConsole.properties b/core/src/main/resources/lib/hudson/scriptConsole.properties index 710fbaf555e1..dc1489ad2abd 100644 --- a/core/src/main/resources/lib/hudson/scriptConsole.properties +++ b/core/src/main/resources/lib/hudson/scriptConsole.properties @@ -21,7 +21,7 @@ # THE SOFTWARE. description=\ - Type in an arbitrary Groovy script and \ + Type in an arbitrary Groovy script and \ execute it on the server. Useful for trouble-shooting and diagnostics. \ Use the \u2018println\u2019 command to see the output (if you use System.out, \ it will go to the server\u2019s stdout, which is harder to see.) Example: diff --git a/core/src/main/resources/lib/hudson/scriptConsole_bg.properties b/core/src/main/resources/lib/hudson/scriptConsole_bg.properties index be4c62b7720f..ca0923890d46 100644 --- a/core/src/main/resources/lib/hudson/scriptConsole_bg.properties +++ b/core/src/main/resources/lib/hudson/scriptConsole_bg.properties @@ -33,12 +33,12 @@ description2=\ It\ is\ not\ possible\ to\ run\ scripts\ when\ slave\ is\ offline.=\ \u041a\u043e\u0433\u0430\u0442\u043e \u043f\u043e\u0434\u0447\u0438\u043d\u0435\u043d\u0438\u044f\u0442 \u043a\u043e\u043c\u043f\u044e\u0442\u044a\u0440 \u043d\u0435 \u0435 \u043d\u0430 \u043b\u0438\u043d\u0438\u044f, \u043d\u0435 \u043c\u043e\u0436\u0435 \u0434\u0430 \u0441\u0435 \u0438\u0437\u043f\u044a\u043b\u043d\u044f\u0432\u0430\u0442 \u0441\u043a\u0440\u0438\u043f\u0442\u043e\u0432\u0435. # \ -# Type in an arbitrary Groovy script and \ +# Type in an arbitrary Groovy script and \ # execute it on the server. Useful for trouble-shooting and diagnostics. \ # Use the \u2018println\u2019 command to see the output (if you use System.out, \ # it will go to the server\u2019s stdout, which is harder to see.) Example: description=\ - \u0412\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u043b\u0435\u043d \u0441\u043a\u0440\u0438\u043f\u0442 \u043d\u0430 Groovy\ + \u0412\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u043b\u0435\u043d \u0441\u043a\u0440\u0438\u043f\u0442 \u043d\u0430 Groovy\ \u0438 \u0433\u043e \u0438\u0437\u043f\u044a\u043b\u043d\u0435\u0442\u0435 \u043d\u0430 \u0441\u044a\u0440\u0432\u044a\u0440\u0430. \u041f\u043e\u043b\u0435\u0437\u043d\u043e \u0435 \u0437\u0430 \u0434\u0438\u0430\u0433\u043d\u043e\u0441\u0442\u0438\u043a\u0430 \u0438 \u043a\u043e\u0440\u0438\u0433\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0438.\ \u0417\u0430 \u0434\u0430 \u0438\u0437\u0432\u0435\u0434\u0435\u0442\u0435 \u0434\u0430\u043d\u043d\u0438 \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0439\u0442\u0435 \u043a\u043e\u043c\u0430\u043d\u0434\u0430\u0442\u0430 \u201eprintln\u201c. (System.out\ \u043e\u0442\u0438\u0432\u0430 \u043d\u0430 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u0438\u044f \u0438\u0437\u0445\u043e\u0434 \u043d\u0430 \u0441\u044a\u0440\u0432\u044a\u0440\u0430, \u043a\u043e\u0435\u0442\u043e \u0441\u0435 \u0441\u043b\u0435\u0434\u0438 \u043f\u043e-\u0442\u0440\u0443\u0434\u043d\u043e.) \u041f\u0440\u0438\u043c\u0435\u0440: diff --git a/core/src/main/resources/lib/hudson/scriptConsole_da.properties b/core/src/main/resources/lib/hudson/scriptConsole_da.properties index cf1afc4cbb27..400c3e7123cb 100644 --- a/core/src/main/resources/lib/hudson/scriptConsole_da.properties +++ b/core/src/main/resources/lib/hudson/scriptConsole_da.properties @@ -23,7 +23,7 @@ Result=Resultat Run=K\u00f8r Script\ Console=Skriptkonsol -description=Indtast et Groovyskript og \ +description=Indtast et Groovyskript og \ k\u00f8r det p\u00e5 serveren. Nyttigt til fejlfinding og diagnostik. \ Benyt ''println'' kommandoen for at se output (hvis du bruger System.out, \ vil output g\u00e5 til serverens stdout, hvilket kan v\u00e6re sv\u00e6rere at finde.) Eksempel: diff --git a/core/src/main/resources/lib/hudson/scriptConsole_de.properties b/core/src/main/resources/lib/hudson/scriptConsole_de.properties index 6505dca93e5c..1cc2ebf2d2c0 100644 --- a/core/src/main/resources/lib/hudson/scriptConsole_de.properties +++ b/core/src/main/resources/lib/hudson/scriptConsole_de.properties @@ -23,7 +23,7 @@ Script\ Console=Skript-Konsole Result=Ergebnis Run=Ausf\u00FChren -description=Geben Sie ein beliebiges Groovy-Skript \ +description=Geben Sie ein beliebiges Groovy-Skript \ ein und f\u00FChren Sie dieses auf dem Server aus. Dies ist n\u00FCtzlich bei der Fehlersuche und zur Diagnostik. \ Verwenden Sie den println-Befehl, um Ausgaben sichtbar zu machen (wenn Sie System.out \ verwenden, gehen die Ausgaben auf die Standardausgabe (STDOUT) des Servers, die schwieriger \ diff --git a/core/src/main/resources/lib/hudson/scriptConsole_es.properties b/core/src/main/resources/lib/hudson/scriptConsole_es.properties index d7df2acb0d19..ddbe14b39f77 100644 --- a/core/src/main/resources/lib/hudson/scriptConsole_es.properties +++ b/core/src/main/resources/lib/hudson/scriptConsole_es.properties @@ -21,7 +21,7 @@ # THE SOFTWARE. description=\ - Escribe un ''script'' Groovy script y \ + Escribe un ''script'' Groovy script y \ ejecutal en el servidor. Es til para depurar e investigar problemas. \ Usa ''println'' para ver la salida (si usas System.out, se escribir \ en la salida ''stdout'' del servidor, lo que es ms difcil de visualizar). Ejemplo: diff --git a/core/src/main/resources/lib/hudson/scriptConsole_fr.properties b/core/src/main/resources/lib/hudson/scriptConsole_fr.properties index 6f17fa4ae47f..3dc31f8f916a 100644 --- a/core/src/main/resources/lib/hudson/scriptConsole_fr.properties +++ b/core/src/main/resources/lib/hudson/scriptConsole_fr.properties @@ -23,6 +23,6 @@ Script\ Console=Console de script Result=Rsultat Run=Excuter -description=Vous pouvez saisir ici un script Groovy quelconque pour l\u2019ex\u00E9cuter sur le serveur.
Utile pour diagnostiquer et r\u00E9soudre des probl\u00E8mes.
Utilisez la commande "println" pour voir la sortie (si vous utilisez System.out, cela ira vers la sortie standard du serveur, qui est plus complexe \u00E0 retrouver.)
Par exemple : +description=Vous pouvez saisir ici un script Groovy quelconque pour l\u2019ex\u00E9cuter sur le serveur.
Utile pour diagnostiquer et r\u00E9soudre des probl\u00E8mes.
Utilisez la commande "println" pour voir la sortie (si vous utilisez System.out, cela ira vers la sortie standard du serveur, qui est plus complexe \u00E0 retrouver.)
Par exemple : description2=Toutes les classes de tous les plugins sont visibles. jenkins.*, jenkins.model.*, hudson.*, et hudson.model.* sont pr\u00E9-import\u00E9es. diff --git a/core/src/main/resources/lib/hudson/scriptConsole_it.properties b/core/src/main/resources/lib/hudson/scriptConsole_it.properties index fdd1fa0aee31..f00ed34b8623 100644 --- a/core/src/main/resources/lib/hudson/scriptConsole_it.properties +++ b/core/src/main/resources/lib/hudson/scriptConsole_it.properties @@ -21,7 +21,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -description=Immettere uno script \ +description=Immettere uno script \ Groovy arbitrario per eseguirlo sul server. Ci utile per eseguire \ operazioni di risoluzione dei problemi e di diagnostica. Utilizzare il \ comando "println" per visualizzare l''output (se si utilizza \ diff --git a/core/src/main/resources/lib/hudson/scriptConsole_ja.properties b/core/src/main/resources/lib/hudson/scriptConsole_ja.properties index b3c84b2c7dfc..b06f35182d80 100644 --- a/core/src/main/resources/lib/hudson/scriptConsole_ja.properties +++ b/core/src/main/resources/lib/hudson/scriptConsole_ja.properties @@ -24,7 +24,7 @@ Script\ Console=\u30b9\u30af\u30ea\u30d7\u30c8\u30b3\u30f3\u30bd\u30fc\u30eb Run=\u5b9f\u884c Result=\u7d50\u679c description= \ - \u4efb\u610f\u306eGroovy\u30b9\u30af\u30ea\u30d7\u30c8\u3092\u5165\u529b\u3057\u3066\u3001 \ + \u4efb\u610f\u306eGroovy\u30b9\u30af\u30ea\u30d7\u30c8\u3092\u5165\u529b\u3057\u3066\u3001 \ \u30b5\u30fc\u30d0\u30fc\u4e0a\u3067\u5b9f\u884c\u3057\u3066\u304f\u3060\u3055\u3044\u3002\u30c8\u30e9\u30d6\u30eb\u30b7\u30e5\u30fc\u30c6\u30a3\u30f3\u30b0\u3084\u8a3a\u65ad\u306b\u4fbf\u5229\u3067\u3059\u3002 \ \u51fa\u529b\u3092\u898b\u308b\u306b\u306f''println''\u30b3\u30de\u30f3\u30c9\u3092\u4f7f\u7528\u3057\u307e\u3059 \ (System.out\u3092\u4f7f\u7528\u3059\u308b\u3068\u30b5\u30fc\u30d0\u30fc\u306e\u6a19\u6e96\u51fa\u529b\u306b\u51fa\u529b\u3055\u308c\u307e\u3059\u304c\u3001\u898b\u306b\u304f\u3044\u3067\u3059\uff09\u3002\u4f8b: diff --git a/core/src/main/resources/lib/hudson/scriptConsole_ko.properties b/core/src/main/resources/lib/hudson/scriptConsole_ko.properties index 31b7f2002057..9a765078d556 100644 --- a/core/src/main/resources/lib/hudson/scriptConsole_ko.properties +++ b/core/src/main/resources/lib/hudson/scriptConsole_ko.properties @@ -23,4 +23,4 @@ Result=\uACB0\uACFC Run=\uC2E4\uD589 Script\ Console=\uC2A4\uD06C\uB9BD\uD2B8 \uCF58\uC194 -description=\uC784\uC758\uC758 Groovy\uC2A4\uD06C\uB9BD\uD2B8\uB97C \uC785\uB825\uD558\uC5EC \uC11C\uBC84\uC5D0\uC11C \uC2E4\uD589\uD569\uB2C8\uB2E4.
\uBB38\uC81C\uD574\uACB0\uACFC \uC9C4\uB2E8\uC2DC\uC5D0 \uC720\uC6A9\uD569\uB2C8\uB2E4. \uCD9C\uB825\uBB3C\uC744 \uBCF4\uB824\uBA74 ''println'' \uBA85\uB839\uC744 \uC0AC\uC6A9\uD558\uC138\uC694.
(System.out\uB97C \uC0AC\uC6A9\uD558\uBA74 \uC11C\uBC84\uC758 \uD45C\uC900\uCD9C\uB825\uC73C\uB85C \uBCF4\uB0B4\uC9C0\uACE0, \uAC00\uB3C5\uC131\uC774 \uB5A8\uC5B4\uC9D1\uB2C8\uB2E4.)

\uC608\uC81C: +description=\uC784\uC758\uC758 Groovy\uC2A4\uD06C\uB9BD\uD2B8\uB97C \uC785\uB825\uD558\uC5EC \uC11C\uBC84\uC5D0\uC11C \uC2E4\uD589\uD569\uB2C8\uB2E4.
\uBB38\uC81C\uD574\uACB0\uACFC \uC9C4\uB2E8\uC2DC\uC5D0 \uC720\uC6A9\uD569\uB2C8\uB2E4. \uCD9C\uB825\uBB3C\uC744 \uBCF4\uB824\uBA74 ''println'' \uBA85\uB839\uC744 \uC0AC\uC6A9\uD558\uC138\uC694.
(System.out\uB97C \uC0AC\uC6A9\uD558\uBA74 \uC11C\uBC84\uC758 \uD45C\uC900\uCD9C\uB825\uC73C\uB85C \uBCF4\uB0B4\uC9C0\uACE0, \uAC00\uB3C5\uC131\uC774 \uB5A8\uC5B4\uC9D1\uB2C8\uB2E4.)

\uC608\uC81C: diff --git a/core/src/main/resources/lib/hudson/scriptConsole_nb_NO.properties b/core/src/main/resources/lib/hudson/scriptConsole_nb_NO.properties index 4855b5d7f0b8..4156a7f96d21 100644 --- a/core/src/main/resources/lib/hudson/scriptConsole_nb_NO.properties +++ b/core/src/main/resources/lib/hudson/scriptConsole_nb_NO.properties @@ -22,4 +22,4 @@ Run=Kj\u00F8r Script\ Console=Skript-konsoll -description=Skriv inn et vilk\u00E5rlig Groovy script og kj\u00F8r det p\u00E5 serveren. Nyttig i forbindelse med feils\u00F8king og diagnostisering. Bruke "println"-kommandoen for \u00E5 se utdataene. (Hvis du bruker System.out havner det i serverens STDOUT, som er vanskeligere \u00E5 se. Eksempel: +description=Skriv inn et vilk\u00E5rlig Groovy script og kj\u00F8r det p\u00E5 serveren. Nyttig i forbindelse med feils\u00F8king og diagnostisering. Bruke "println"-kommandoen for \u00E5 se utdataene. (Hvis du bruker System.out havner det i serverens STDOUT, som er vanskeligere \u00E5 se. Eksempel: diff --git a/core/src/main/resources/lib/hudson/scriptConsole_nl.properties b/core/src/main/resources/lib/hudson/scriptConsole_nl.properties index 263f9a0fa131..d158314cd638 100644 --- a/core/src/main/resources/lib/hudson/scriptConsole_nl.properties +++ b/core/src/main/resources/lib/hudson/scriptConsole_nl.properties @@ -23,4 +23,4 @@ Script\ Console=Scriptconsole Run=Voer uit Result=Resultaat -description=Geef een willekeurig Groovy script in en voer het uit op de server. Dit is nuttig voor troubleshooting en het stellen van diagnoses. Gebruik het "println"-commando om uitvoer te zien (wanneer je System.out gebruikt, gaat de uitvoer naar de stdout van de server, en deze is moeilijker te bekijken.) Bijvoorbeeld: +description=Geef een willekeurig Groovy script in en voer het uit op de server. Dit is nuttig voor troubleshooting en het stellen van diagnoses. Gebruik het "println"-commando om uitvoer te zien (wanneer je System.out gebruikt, gaat de uitvoer naar de stdout van de server, en deze is moeilijker te bekijken.) Bijvoorbeeld: diff --git a/core/src/main/resources/lib/hudson/scriptConsole_pl.properties b/core/src/main/resources/lib/hudson/scriptConsole_pl.properties index 666edf81bf48..39f1c15d2968 100644 --- a/core/src/main/resources/lib/hudson/scriptConsole_pl.properties +++ b/core/src/main/resources/lib/hudson/scriptConsole_pl.properties @@ -3,6 +3,6 @@ Result=Rezultat Run=Wykonaj Script\ Console=Konsola Skrypt\u00F3w -description=Wpisz skrypt Groovy script i wykonaj go na serwerze. U\u017Cyteczne w przypadku problem\u00F3w i diagnostyki. U\u017Cyj komendy ''''println'''' aby zobaczy\u0107 wynik (je\u015Bli u\u017Cyjesz System.out, wynik b\u0119dzie trudniejszy do znalezienia). Przyk\u0142ad: +description=Wpisz skrypt Groovy script i wykonaj go na serwerze. U\u017Cyteczne w przypadku problem\u00F3w i diagnostyki. U\u017Cyj komendy ''''println'''' aby zobaczy\u0107 wynik (je\u015Bli u\u017Cyjesz System.out, wynik b\u0119dzie trudniejszy do znalezienia). Przyk\u0142ad: description2=Wszystkie klasy ze wszystkich dodatk\u00F3w s\u0105 widoczne. jenkins.*, jenkins.model.*, hudson.*, i hudson.model.* s\u0105 wst\u0119pnie zaimportowane. diff --git a/core/src/main/resources/lib/hudson/scriptConsole_pt_BR.properties b/core/src/main/resources/lib/hudson/scriptConsole_pt_BR.properties index 9013530e50cd..512d2bc7076e 100644 --- a/core/src/main/resources/lib/hudson/scriptConsole_pt_BR.properties +++ b/core/src/main/resources/lib/hudson/scriptConsole_pt_BR.properties @@ -23,11 +23,11 @@ Script\ Console=Console de script Run=Executar Result=Resultado -# Type in an arbitrary Groovy script and \ +# Type in an arbitrary Groovy script and \ # execute it on the server. Useful for trouble-shooting and diagnostics. \ # Use the ''println'' command to see the output (if you use System.out, \ # it will go to the server''s stdout, which is harder to see.) Example: -description=Digite um comando Groovy script qualquer e \ +description=Digite um comando Groovy script qualquer e \ execute-o no servidor. \u00DAtil para resolu\u00E7\u00E3o de problemas e diagn\u00F3sticos. \ Use o comando "println" para ver a sa\u00EDda (se voc\u00EA usa System.out, \ ele ir\u00E1 para o log do servidor, que \u00E9 mais dif\u00EDcil de ver). Exemplo: diff --git a/core/src/main/resources/lib/hudson/scriptConsole_ru.properties b/core/src/main/resources/lib/hudson/scriptConsole_ru.properties index 12ac56e9dde6..bdff9a3fa6cd 100644 --- a/core/src/main/resources/lib/hudson/scriptConsole_ru.properties +++ b/core/src/main/resources/lib/hudson/scriptConsole_ru.properties @@ -3,4 +3,4 @@ Result=\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 Run=\u0417\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c Script\ Console=\u041a\u043e\u043d\u0441\u043e\u043b\u044c description2=\u0412\u0441\u0435 \u043a\u043b\u0430\u0441\u0441\u044b \u0438\u0437 \u0432\u0441\u0435\u0445 \u043f\u043b\u0430\u0433\u0438\u043d\u043e\u0432 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b. jenkins.*, jenkins.model.*, hudson.* \u0438 hudson.model.* \u0443\u0436\u0435 \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u044b \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e. -description=\u0412\u0432\u0435\u0434\u0438\u0442\u0435 Groovy \u0441\u043a\u0440\u0438\u043f\u0442 \u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u0435 \u0435\u0433\u043e \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435. \u041f\u043e\u043b\u0435\u0437\u043d\u043e \u043f\u0440\u0438 \u0443\u0441\u0442\u0440\u0430\u043d\u0435\u043d\u0438\u0438 \u043d\u0435\u043f\u043e\u043b\u0430\u0434\u043e\u043a \u0438 \u0434\u0438\u0430\u0433\u043d\u043e\u0441\u0442\u0438\u043a\u0438. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u043a\u043e\u043c\u0430\u043d\u0434\u0443 ''''println'''' \u0434\u043b\u044f \u043f\u0435\u0447\u0430\u0442\u0438 \u0432 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 \u0432\u044b\u0432\u043e\u0434 (\u0435\u0441\u043b\u0438 \u0432\u044b \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0435\u0441\u044c System.out, \u0442\u043e \u0432\u044b\u0432\u043e\u0434 \u043f\u043e\u0439\u0434\u0451\u0442 \u0432 stdout \u0441\u0435\u0440\u0432\u0435\u0440\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u043b\u043e\u0436\u043d\u0435\u0435 \u0443\u0432\u0438\u0434\u0435\u0442\u044c). \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440: +description=\u0412\u0432\u0435\u0434\u0438\u0442\u0435 Groovy \u0441\u043a\u0440\u0438\u043f\u0442 \u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u0435 \u0435\u0433\u043e \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435. \u041f\u043e\u043b\u0435\u0437\u043d\u043e \u043f\u0440\u0438 \u0443\u0441\u0442\u0440\u0430\u043d\u0435\u043d\u0438\u0438 \u043d\u0435\u043f\u043e\u043b\u0430\u0434\u043e\u043a \u0438 \u0434\u0438\u0430\u0433\u043d\u043e\u0441\u0442\u0438\u043a\u0438. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u043a\u043e\u043c\u0430\u043d\u0434\u0443 ''''println'''' \u0434\u043b\u044f \u043f\u0435\u0447\u0430\u0442\u0438 \u0432 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 \u0432\u044b\u0432\u043e\u0434 (\u0435\u0441\u043b\u0438 \u0432\u044b \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0435\u0441\u044c System.out, \u0442\u043e \u0432\u044b\u0432\u043e\u0434 \u043f\u043e\u0439\u0434\u0451\u0442 \u0432 stdout \u0441\u0435\u0440\u0432\u0435\u0440\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u043b\u043e\u0436\u043d\u0435\u0435 \u0443\u0432\u0438\u0434\u0435\u0442\u044c). \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440: diff --git a/core/src/main/resources/lib/hudson/scriptConsole_sr.properties b/core/src/main/resources/lib/hudson/scriptConsole_sr.properties index d460d45ff9f8..8b7a38680b57 100644 --- a/core/src/main/resources/lib/hudson/scriptConsole_sr.properties +++ b/core/src/main/resources/lib/hudson/scriptConsole_sr.properties @@ -2,7 +2,7 @@ Script\ Console=\u041A\u043E\u043D\u0437\u043E\u043B\u0430 description=\ - \u0423\u043D\u0435\u0441\u0438\u0442\u0435 \u0430\u0440\u0431\u0438\u0442\u0440\u0430\u0440\u043D\u0438 Groovy \u0441\u043A\u0440\u0443\u043F\u0442\u0443 \u0438 \ + \u0423\u043D\u0435\u0441\u0438\u0442\u0435 \u0430\u0440\u0431\u0438\u0442\u0440\u0430\u0440\u043D\u0438 Groovy \u0441\u043A\u0440\u0443\u043F\u0442\u0443 \u0438 \ \u0438\u0437\u0432\u0440\u0448\u0438 \u0458\u0435 \u043D\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0443. \u041A\u043E\u0440\u0438\u0441\u043D\u043E \u043A\u043E\u0434 \u0440\u0435\u0448\u0430\u0432\u0430\u045A\u0435 \u043F\u0440\u043E\u0431\u043B\u0435\u043C\u0430 \u0438 \u0434\u0438\u0430\u0433\u043D\u043E\u0441\u0442\u0438\u043A\u043E\u043C. \ \u041A\u043E\u0440\u0438\u0441\u0442\u0438\u0442\u0435 \u043A\u043E\u043C\u0430\u043D\u0434\u0443 \u2018println\u2019 \u0434\u0430 \u0432\u0438\u0434\u0438\u0442\u0435 \u0438\u0441\u0445\u043E\u0434 (\u0431\u0438\u045B\u0435 \u043F\u0440\u0435\u0443\u0441\u043C\u0435\u0440\u0435\u043D\u043E \ System.out \u043F\u0440\u0435\u043C\u0430 stdout \u0441\u0435\u0440\u0432\u0435\u0440\u0430, \u0448\u0442\u043E \u0441\u0435 \u0442\u0435\u0436\u0435 \u0447\u0438\u0442\u0430.) \u041F\u0440\u0438\u043C\u0435\u0440: diff --git a/core/src/main/resources/lib/hudson/scriptConsole_sv_SE.properties b/core/src/main/resources/lib/hudson/scriptConsole_sv_SE.properties index eb6893c691d2..38aab9e28b9f 100644 --- a/core/src/main/resources/lib/hudson/scriptConsole_sv_SE.properties +++ b/core/src/main/resources/lib/hudson/scriptConsole_sv_SE.properties @@ -23,5 +23,5 @@ Result=Resultat Run=K\u00F6r Script\ Console=Skriptkonsoll -description=Skriv in ett godtyckligt Groovy skript och k\u00F6r den p\u00E5 noden. Detta \u00E4r anv\u00E4ndbart vid fels\u00F6kning och diagnostik. Anv\u00E4nd''''println''''kommandot f\u00F6r att se resultatet (om du anv\u00E4nder System.out kommer det att g\u00E5 till serverns standard ut, som \u00E4r sv\u00E5rare att se.) Exempel: +description=Skriv in ett godtyckligt Groovy skript och k\u00F6r den p\u00E5 noden. Detta \u00E4r anv\u00E4ndbart vid fels\u00F6kning och diagnostik. Anv\u00E4nd''''println''''kommandot f\u00F6r att se resultatet (om du anv\u00E4nder System.out kommer det att g\u00E5 till serverns standard ut, som \u00E4r sv\u00E5rare att se.) Exempel: description2=Alla klasser fr\u00E5n pluginet \u00E4r synliga. jenkins.*, jenkins.model.*, hudson.*, and hudson.model.* are pre-imported. diff --git a/core/src/main/resources/lib/hudson/scriptConsole_zh_TW.properties b/core/src/main/resources/lib/hudson/scriptConsole_zh_TW.properties index 6e968873db38..ba14d84e2b22 100644 --- a/core/src/main/resources/lib/hudson/scriptConsole_zh_TW.properties +++ b/core/src/main/resources/lib/hudson/scriptConsole_zh_TW.properties @@ -23,7 +23,7 @@ Script\ Console=Script \u4e3b\u63a7\u53f0 description=\ - \u8f38\u5165\u4efb\u610f\u7684 Groovy Script\uff0c\u5728\u4f3a\u670d\u5668\u4e0a\u57f7\u884c\u3002\ + \u8f38\u5165\u4efb\u610f\u7684 Groovy Script\uff0c\u5728\u4f3a\u670d\u5668\u4e0a\u57f7\u884c\u3002\ \u7591\u96e3\u6392\u89e3\u6216\u8a3a\u65b7\u6642\u5f88\u6709\u7528\u3002\u4f7f\u7528 "println" \u6307\u4ee4\u53ef\u4ee5\u770b\u5230\u8f38\u51fa\u7d50\u679c \ (\u5982\u679c\u60a8\u4f7f\u7528 System.out\uff0c\u5c07\u8f38\u51fa\u5230\u4f3a\u670d\u5668\u4e0a\u7684 stdout\uff0c\u6703\u6bd4\u8f03\u96e3\u770b\u5230)\u3002\u7bc4\u4f8b: description2=\u6240\u6709\u5916\u639B\u7A0B\u5F0F\u7684 class \u90FD\u53EF\u4EE5\u4F7F\u7528\u3002\u5DF2\u9810\u5148 import \u4E86 jenkins.*, jenkins.model.*, hudson.* \u53CA hudson.model.* \u7B49 package\u3002 diff --git a/war/src/main/fonts/font-awesome/css/font-awesome.css b/war/src/main/fonts/font-awesome/css/font-awesome.css index 3638b7f95522..106cf81c0463 100644 --- a/war/src/main/fonts/font-awesome/css/font-awesome.css +++ b/war/src/main/fonts/font-awesome/css/font-awesome.css @@ -1,6 +1,6 @@ /*! - * Font Awesome 4.6.2 by @davegandy - http://fontawesome.io - @fontawesome - * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + * Font Awesome 4.6.2 by @davegandy - https://fontawesome.com - @fontawesome + * License - https://fontawesome.com/license (Font: SIL OFL 1.1, CSS: MIT License) */ /* FONT PATH * -------------------------- */ diff --git a/war/src/main/fonts/font-awesome/css/font-awesome.min.css b/war/src/main/fonts/font-awesome/css/font-awesome.min.css index 9c3cf74241f9..73a0ad65662b 100644 --- a/war/src/main/fonts/font-awesome/css/font-awesome.min.css +++ b/war/src/main/fonts/font-awesome/css/font-awesome.min.css @@ -1,4 +1,4 @@ /*! - * Font Awesome 4.6.2 by @davegandy - http://fontawesome.io - @fontawesome - * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + * Font Awesome 4.6.2 by @davegandy - https://fontawesome.com - @fontawesome + * License - https://fontawesome.com/license (Font: SIL OFL 1.1, CSS: MIT License) */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.6.2');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.6.2') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.6.2') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.6.2') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.6.2') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.6.2#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} diff --git a/war/src/main/webapp/help/system-config/master-slave/jnlp-tunnel.html b/war/src/main/webapp/help/system-config/master-slave/jnlp-tunnel.html index a3b780812c29..d9c9c8f64615 100644 --- a/war/src/main/webapp/help/system-config/master-slave/jnlp-tunnel.html +++ b/war/src/main/webapp/help/system-config/master-slave/jnlp-tunnel.html @@ -3,8 +3,8 @@ to establish a communication channel. But some security sensitive network can prevent you from making this connection. This can also happen when Jenkins runs behind a load balancer, - apache reverse proxy - into DMZ, and so on. + apache reverse proxy + into DMZ, and so on.

This tunneling option allows you to route this connection to another host/port, and useful for those situations. diff --git a/war/src/main/webapp/help/system-config/master-slave/jnlp-tunnel_bg.html b/war/src/main/webapp/help/system-config/master-slave/jnlp-tunnel_bg.html index 53c11117b01a..6708516d6570 100644 --- a/war/src/main/webapp/help/system-config/master-slave/jnlp-tunnel_bg.html +++ b/war/src/main/webapp/help/system-config/master-slave/jnlp-tunnel_bg.html @@ -4,8 +4,8 @@ Някои мрежи с повишено ниво на сигурност могат да предотвратят това. Това може да се случи също и когато Jenkins е зад машина, разпределяща натоварването, - обратен сървър посредник, - в демилитаризираната зона( + обратен сървър посредник, + в демилитаризираната зона( и т.н.

diff --git a/war/src/main/webapp/help/system-config/master-slave/jnlp-tunnel_de.html b/war/src/main/webapp/help/system-config/master-slave/jnlp-tunnel_de.html index 3b673af19eb8..ccf75294580c 100644 --- a/war/src/main/webapp/help/system-config/master-slave/jnlp-tunnel_de.html +++ b/war/src/main/webapp/help/system-config/master-slave/jnlp-tunnel_de.html @@ -2,8 +2,8 @@ Wird ein Agent über JNLP gestartet, versucht dieser über einen bestimmten TCP-Port den Jenkins-Master zu erreichen. In abgesicherten Netzwerken kann dieser Verbindungsaufbau jedoch scheitern. Probleme können auch dadurch entstehen, wenn Jenkins hinter einem Lastverteiler (load balancer), - einem Apache Reverse-Proxy - in eine DMZ o.ä. + einem Apache Reverse-Proxy + in eine DMZ o.ä. betrieben wird.

@@ -22,4 +22,4 @@ das HOST:-Format ist sehr praktisch, wenn ein HTTP-Reverse-Proxy verwendet wird und Jenkins auf einem anderen Rechner läuft. -

\ No newline at end of file +
diff --git a/war/src/main/webapp/help/system-config/master-slave/jnlp-tunnel_fr.html b/war/src/main/webapp/help/system-config/master-slave/jnlp-tunnel_fr.html index e04fd84749ed..b261bb9cfd53 100644 --- a/war/src/main/webapp/help/system-config/master-slave/jnlp-tunnel_fr.html +++ b/war/src/main/webapp/help/system-config/master-slave/jnlp-tunnel_fr.html @@ -3,8 +3,8 @@ TCP spécifique, afin d'établir un canal de communication. Certains réseaux très sécurisés peuvent vous empècher d'effectuer cette connexion. Cela peut également être le cas quand Jenkins tourne derrière un répartiteur de charge (load balancer), - un reverse proxy apache - dans une zone démilitarisée (DMZ), etc. + un reverse proxy apache + dans une zone démilitarisée (DMZ), etc.

Cette option de tunneling vous permet, dans ces situations, de rediriger la connexion sur un autre host ou @@ -18,4 +18,4 @@ Jenkins utilise et le numéro de port que Jenkins a ouvert) sont utilisés pour compléter les valeurs manquantes. En particulier, le format HOST: est utile si un reverse proxy HTTP est utilisé et que Jenkins tourne en fait sur un autre système. -

\ No newline at end of file +
diff --git a/war/src/main/webapp/help/system-config/master-slave/jnlp-tunnel_it.html b/war/src/main/webapp/help/system-config/master-slave/jnlp-tunnel_it.html index 710f7452f526..6b5de58ed577 100644 --- a/war/src/main/webapp/help/system-config/master-slave/jnlp-tunnel_it.html +++ b/war/src/main/webapp/help/system-config/master-slave/jnlp-tunnel_it.html @@ -3,8 +3,8 @@ Jenkins per stabilire un canale di comunicazione. Alcune reti configurate in modo sicuro, tuttavia, possono impedire l'instaurazione di questa connessione. Ciò può succedere anche quando Jenkins è in esecuzione mascherato da un bilanciatore di carico, - da un proxy inverso Apache - è in una DMZ, e così via. + da un proxy inverso Apache + è in una DMZ, e così via.

Quest'opzione di tunneling consente di instradare questa connessione a un altro host/porta ed è utile in diff --git a/war/src/main/webapp/help/system-config/master-slave/jnlp-tunnel_ja.html b/war/src/main/webapp/help/system-config/master-slave/jnlp-tunnel_ja.html index 3e38f9bf0850..6d1c00243de0 100644 --- a/war/src/main/webapp/help/system-config/master-slave/jnlp-tunnel_ja.html +++ b/war/src/main/webapp/help/system-config/master-slave/jnlp-tunnel_ja.html @@ -2,8 +2,8 @@ エージェントがJNLPで起動するとき、エージェントは通信チャネルを確立するために、Jenkinsのある特定のTCPポートに接続しようとします。 しかし、セキュリティに神経質なネットワークでは接続できないことがあります。 これは、Jenkinsがロードバランサーや - DMZへの - Apacheリバースプロクシーの背後にある場合などにも生じます。 + DMZへの + Apacheリバースプロクシーの背後にある場合などにも生じます。

このトンネルオプションを設定すると、コネクションを他のホストやポート経由にすることができ、上記のような状況で役に立ちます。 @@ -15,4 +15,4 @@ 残りの2つの形式では、設定していないホストもしくはポートの値に、デフォルトのホスト名やポート番号 (すなわち、Jenkinsが動作しているホスト名やJenkinsがオープンしているTCPポート)を追加します。 特に、HTTPリバースプロクシーを使用していて、実際にはJenkinsが他のシステムで動作している場合には、"ホスト:"の形式は役に立ちます。 -

\ No newline at end of file + diff --git a/war/src/main/webapp/help/system-config/master-slave/jnlp-tunnel_zh_TW.html b/war/src/main/webapp/help/system-config/master-slave/jnlp-tunnel_zh_TW.html index 08404f98ec01..553d90c278ca 100644 --- a/war/src/main/webapp/help/system-config/master-slave/jnlp-tunnel_zh_TW.html +++ b/war/src/main/webapp/help/system-config/master-slave/jnlp-tunnel_zh_TW.html @@ -2,8 +2,8 @@ Agent 透過 JNLP 啟動時,其代理程式會試著連回 Jenkins 特定的 TCP 連接埠,建立通訊通道。 但是某些安全敏感的網路可能不能讓您這樣建立連線。 Jenkins 在負載平衡器、 - Apache 反向代理到 - DMZ 等環境中也可能無法建立連線。 + Apache 反向代理到 + DMZ 等環境中也可能無法建立連線。

Tunneling 選項可以讓您把連線繞到其他主機或連接埠,適用上述幾種情境。 @@ -13,4 +13,4 @@

後面兩種格式中,不指定的部分就代表使用預設值 (主機部分就是 Jenkins 執行的機器,TCP 連接埠就是 Jenkins 開啟的那個)。 HOST: 格式幾乎是專為 Jenkins 在別的機器上跑的 HTTP 反向代理環境設計的。 - \ No newline at end of file + diff --git a/war/src/main/webapp/scripts/yui/yahoo/yahoo-debug.js b/war/src/main/webapp/scripts/yui/yahoo/yahoo-debug.js index e7f786db4223..82990bd5b46d 100644 --- a/war/src/main/webapp/scripts/yui/yahoo/yahoo-debug.js +++ b/war/src/main/webapp/scripts/yui/yahoo/yahoo-debug.js @@ -324,7 +324,7 @@ YAHOO.env.parseUA = function(agent) { * Webkit nightly 1/2008:525+ <-- Supports DOMContentLoaded event. * yahoo.com user agent hack removed. * - * http://en.wikipedia.org/wiki/Safari_version_history + * https://en.wikipedia.org/wiki/Safari_version_history * @property webkit * @type float * @static @@ -728,7 +728,7 @@ return (o && (typeof o === 'object' || L.isFunction(o))) || false; * *

* This implementation is based on the - * OWASP + * OWASP * HTML escaping recommendations. In addition to the characters * in the OWASP recommendation, we also escape the ` * character, since IE interprets it as an attribute delimiter when used in From 2ad5d26c92310d8b7c9bd5682310e29d1b480434 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 4 Nov 2021 21:16:19 +0100 Subject: [PATCH 37/44] Fix missing project hyperlink in build history (#5875) --- .../main/resources/hudson/widgets/HistoryWidget/entry.jelly | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/resources/hudson/widgets/HistoryWidget/entry.jelly b/core/src/main/resources/hudson/widgets/HistoryWidget/entry.jelly index 2a03d82e9e6c..b690e2084ccc 100644 --- a/core/src/main/resources/hudson/widgets/HistoryWidget/entry.jelly +++ b/core/src/main/resources/hudson/widgets/HistoryWidget/entry.jelly @@ -47,9 +47,9 @@ THE SOFTWARE. ${%Took} ${build.durationString} - + ${h.getUserTimeZonePostfix()} - + From fb93a9699774e2372ff67f791f14ae994eb2bab7 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 4 Nov 2021 21:16:47 +0100 Subject: [PATCH 38/44] Use vectorized sprite on rage (#5878) --- .../jenkins/model/Jenkins/oops.jelly | 2 +- war/src/main/webapp/images/rage.png | Bin 27064 -> 0 bytes war/src/main/webapp/images/rage.svg | 100 ++++++++++++++++++ 3 files changed, 101 insertions(+), 1 deletion(-) delete mode 100644 war/src/main/webapp/images/rage.png create mode 100644 war/src/main/webapp/images/rage.svg diff --git a/core/src/main/resources/jenkins/model/Jenkins/oops.jelly b/core/src/main/resources/jenkins/model/Jenkins/oops.jelly index f8cbb84497b7..28238c660253 100644 --- a/core/src/main/resources/jenkins/model/Jenkins/oops.jelly +++ b/core/src/main/resources/jenkins/model/Jenkins/oops.jelly @@ -32,7 +32,7 @@ THE SOFTWARE.

- ${%Oops!} + ${%Oops!}

${%problemHappened}

diff --git a/war/src/main/webapp/images/rage.png b/war/src/main/webapp/images/rage.png deleted file mode 100644 index ac3cdc3a7439a1f81647df2a91f8a6f39a4eb6e0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27064 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 0813282d680729e2371af6014bc9a85c6728090f Mon Sep 17 00:00:00 2001 From: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Fri, 5 Nov 2021 07:15:36 +0000 Subject: [PATCH 39/44] Update spinner component across Jenkins (#5876) Co-authored-by: Tim Jacomb --- .../hudson/model/Job/configure.jelly | 2 +- .../ParametersDefinitionProperty/index.jelly | 2 +- .../hudson/model/Run/configure.jelly | 2 +- .../resources/hudson/model/Run/console.jelly | 4 +- .../hudson/model/TaskAction/log.jelly | 2 +- .../GlobalSecurityConfiguration/index.groovy | 4 +- .../hudson/slaves/SlaveComputer/log.jelly | 2 +- .../log.groovy | 2 +- .../GlobalCloudConfiguration/index.groovy | 4 +- .../jenkins/model/Jenkins/configure.jelly | 2 +- .../GlobalToolConfiguration/index.groovy | 4 +- .../main/resources/lib/form/helpArea.jelly | 6 +- .../resources/lib/form/validateButton.jelly | 2 +- .../main/resources/lib/layout/spinner.jelly | 36 +++++++ war/src/main/less/base-styles-v2.less | 1 + war/src/main/less/base/style.less | 99 +++---------------- war/src/main/less/modules/form.less | 8 +- war/src/main/less/modules/icons.less | 2 +- war/src/main/less/modules/spinner.less | 43 ++++++++ .../main/webapp/scripts/hudson-behavior.js | 2 +- 20 files changed, 127 insertions(+), 102 deletions(-) create mode 100644 core/src/main/resources/lib/layout/spinner.jelly create mode 100644 war/src/main/less/modules/spinner.less diff --git a/core/src/main/resources/hudson/model/Job/configure.jelly b/core/src/main/resources/hudson/model/Job/configure.jelly index b4d8be5a0535..65d855c9e454 100644 --- a/core/src/main/resources/hudson/model/Job/configure.jelly +++ b/core/src/main/resources/hudson/model/Job/configure.jelly @@ -39,7 +39,7 @@ THE SOFTWARE.
-
${%LOADING}
+
diff --git a/core/src/main/resources/hudson/model/ParametersDefinitionProperty/index.jelly b/core/src/main/resources/hudson/model/ParametersDefinitionProperty/index.jelly index e2f26050a6ae..7a6dea4def1f 100644 --- a/core/src/main/resources/hudson/model/ParametersDefinitionProperty/index.jelly +++ b/core/src/main/resources/hudson/model/ParametersDefinitionProperty/index.jelly @@ -38,7 +38,7 @@ THE SOFTWARE. -
${%LOADING}
+

${it.job.pronoun} ${it.job.displayName}

${%description}

diff --git a/core/src/main/resources/hudson/model/Run/configure.jelly b/core/src/main/resources/hudson/model/Run/configure.jelly index a62494b48505..3976538b27a9 100644 --- a/core/src/main/resources/hudson/model/Run/configure.jelly +++ b/core/src/main/resources/hudson/model/Run/configure.jelly @@ -27,7 +27,7 @@ THE SOFTWARE. -
${%LOADING}
+
diff --git a/core/src/main/resources/hudson/model/Run/console.jelly b/core/src/main/resources/hudson/model/Run/console.jelly index a8e6e995735a..f5c7b4ec362e 100644 --- a/core/src/main/resources/hudson/model/Run/console.jelly +++ b/core/src/main/resources/hudson/model/Run/console.jelly @@ -51,8 +51,8 @@ THE SOFTWARE.
-            
- +
+
diff --git a/core/src/main/resources/hudson/model/TaskAction/log.jelly b/core/src/main/resources/hudson/model/TaskAction/log.jelly index f7be5cfb7a1d..820625fcc08e 100644 --- a/core/src/main/resources/hudson/model/TaskAction/log.jelly +++ b/core/src/main/resources/hudson/model/TaskAction/log.jelly @@ -32,7 +32,7 @@ THE SOFTWARE.
       
- +
diff --git a/core/src/main/resources/hudson/security/GlobalSecurityConfiguration/index.groovy b/core/src/main/resources/hudson/security/GlobalSecurityConfiguration/index.groovy index 0d1634c99e32..3bfb4b284eb1 100644 --- a/core/src/main/resources/hudson/security/GlobalSecurityConfiguration/index.groovy +++ b/core/src/main/resources/hudson/security/GlobalSecurityConfiguration/index.groovy @@ -20,7 +20,9 @@ l.layout(permission:app.SYSTEM_READ, title:my.displayName, cssclass:request.getP set("readOnlyMode", !app.hasPermission(app.ADMINISTER)) p() - div(class:"behavior-loading", _("LOADING")) + div(class:"behavior-loading") { + l.spinner(text: _("LOADING")) + } f.form(method:"post",name:"config",action:"configure") { set("instance",my) set("descriptor", my.descriptor) diff --git a/core/src/main/resources/hudson/slaves/SlaveComputer/log.jelly b/core/src/main/resources/hudson/slaves/SlaveComputer/log.jelly index e2f20376b1c8..aad362b85189 100644 --- a/core/src/main/resources/hudson/slaves/SlaveComputer/log.jelly +++ b/core/src/main/resources/hudson/slaves/SlaveComputer/log.jelly @@ -29,7 +29,7 @@ THE SOFTWARE.
       
- +
- + Place holder to lazy-load help text via AJAX.
-
${%Loading...}
+
+ +
diff --git a/core/src/main/resources/lib/form/validateButton.jelly b/core/src/main/resources/lib/form/validateButton.jelly index 21c84e738c48..ba8f19252703 100644 --- a/core/src/main/resources/lib/form/validateButton.jelly +++ b/core/src/main/resources/lib/form/validateButton.jelly @@ -58,7 +58,7 @@ THE SOFTWARE. data-validate-button-with="${with}" />
- ${attrs.progress} +
diff --git a/core/src/main/resources/lib/layout/spinner.jelly b/core/src/main/resources/lib/layout/spinner.jelly new file mode 100644 index 000000000000..1515f577bee6 --- /dev/null +++ b/core/src/main/resources/lib/layout/spinner.jelly @@ -0,0 +1,36 @@ + + + + + + Customizable spinner component + + Text to use alongside the spinner + + +

+ ${text} +

+
diff --git a/war/src/main/less/base-styles-v2.less b/war/src/main/less/base-styles-v2.less index c63239b1099a..70dabfd03a6c 100644 --- a/war/src/main/less/base-styles-v2.less +++ b/war/src/main/less/base-styles-v2.less @@ -32,6 +32,7 @@ html { @import './modules/section.less'; @import './modules/side-panel-tasks.less'; @import './modules/side-panel-widgets.less'; +@import './modules/spinner.less'; @import './modules/tabs.less'; @import './modules/tooltips.less'; @import './modules/panes-and-bigtable.less'; diff --git a/war/src/main/less/base/style.less b/war/src/main/less/base/style.less index 20bb1ba6ddf2..80d7e860f6f1 100644 --- a/war/src/main/less/base/style.less +++ b/war/src/main/less/base/style.less @@ -421,24 +421,6 @@ pre.console { color: gray; } -.spinner { - padding-left: 32px; - padding-top: 0.5em; - padding-bottom: 0.5em; - background-image: url("../../images/spinner.gif"); - background-repeat: no-repeat; - background-position: left; -} - -.spinner-right { - padding-right: 32px; - padding-top: 0.5em; - padding-bottom: 0.5em; - background-image: url("../../images/spinner.gif"); - background-repeat: no-repeat; - background-position: right; -} - #login-field { vertical-align: middle; padding-right: 1em; @@ -459,74 +441,25 @@ pre.console { } div.behavior-loading { - position: absolute; - left: 0; - right: 0; - width: 100%; - height: 100%; - background-color: #e4e4e4; - text-align: center; - // TODO: use a display font, maybe 3rem - font-size: 300%; - opacity: 0.5; - -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)"; - filter: alpha(opacity=50); -} + position: fixed; + display: flex; + align-items: center; + justify-content: center; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 10; -.config div.behavior-loading { - background: rgba(255, 255, 255, 0.85); + &::before { + content: ""; + position: absolute; + top: 0; left: 0; - width: 100%; - right: 0; - top: 15px; bottom: 0; - height: 100%; - width: auto; - min-height: 100%; - -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; - filter: alpha(opacity=100); - font-size: 1.5em; - z-index: 99; - opacity: 1; - color: #999; - text-shadow: #fff 0 0 5px, #fff 0 0 5px, #fff 0 0 5px, #fff 0 0 5px, #fff 0 0 5px; -} - -.behavior-loading::before { - content: ' '; - display: block; - width: 100px; - height: 100px; - margin: 200px auto 25px; - background-color: rgba(0, 0, 0, 0.15); - border: 5px solid rgba(0, 0, 0, 0.33); - position: relative; - z-index: 2; - border-radius: 100%; - -webkit-animation: sk-scaleout 1s infinite ease-in-out; - animation: sk-scaleout 1s infinite ease-in-out; - box-shadow: #fff 0 0 0 10px; -} - -@-webkit-keyframes sk-scaleout { - 0% { -webkit-transform: scale(0); } - - 100% { - -webkit-transform: scale(1); - opacity: 0; - } -} - -@keyframes sk-scaleout { - 0% { - -webkit-transform: scale(0); - transform: scale(0); - } - - 100% { - -webkit-transform: scale(1); - transform: scale(1); - opacity: 0; + right: 0; + background: var(--background); + opacity: 0.95; } } diff --git a/war/src/main/less/modules/form.less b/war/src/main/less/modules/form.less index c77b279b0a9f..171908a2ae55 100644 --- a/war/src/main/less/modules/form.less +++ b/war/src/main/less/modules/form.less @@ -150,10 +150,14 @@ color: inherit; border: 2px solid currentColor; border-radius: 100%; - animation: loading-spinner 0.5s infinite linear; - clip-path: inset(0 0 8px 8px); + clip-path: inset(0 0 50% 50%); opacity: 0; transition: var(--standard-transition); + animation: loading-spinner 1s infinite linear; + + @media (prefers-reduced-motion) { + animation-duration: 2s; + } } &--loading { diff --git a/war/src/main/less/modules/icons.less b/war/src/main/less/modules/icons.less index 19a1782c0a74..cc558ad14d85 100644 --- a/war/src/main/less/modules/icons.less +++ b/war/src/main/less/modules/icons.less @@ -114,6 +114,6 @@ .icon-disabled-anime .build-status-icon__outer, .icon-nobuilt-anime .build-status-icon__outer { .svg-icon { - animation: spin 2s linear infinite; + animation: spin 1.5s linear infinite; } } diff --git a/war/src/main/less/modules/spinner.less b/war/src/main/less/modules/spinner.less new file mode 100644 index 000000000000..aba98cd8c586 --- /dev/null +++ b/war/src/main/less/modules/spinner.less @@ -0,0 +1,43 @@ +.jenkins-spinner { + position: relative; + display: inline-flex; + align-items: center; + font-size: 0.7rem; + letter-spacing: 2px; + font-weight: 700; + text-transform: uppercase; + margin: 0; + + &::before, &::after { + content: ""; + display: inline-block; + width: 20px; + height: 20px; + border-radius: 100%; + border: 2px solid currentColor; + } + + &::before { + position: relative; + margin-right: 0.75rem; + opacity: 0.2; + } + + &::after { + position: absolute; + top: 0; + left: 0; + clip-path: inset(0 0 50% 50%); + animation: loading-spinner 1s infinite linear; + + @media (prefers-reduced-motion) { + animation-duration: 2s; + } + } +} + +@keyframes loading-spinner { + to { + transform: rotate(360deg); + } +} diff --git a/war/src/main/webapp/scripts/hudson-behavior.js b/war/src/main/webapp/scripts/hudson-behavior.js index 1b912575ced4..4b5da22d9a81 100644 --- a/war/src/main/webapp/scripts/hudson-behavior.js +++ b/war/src/main/webapp/scripts/hudson-behavior.js @@ -1587,7 +1587,7 @@ function xor(a,b) { // used by editableDescription.jelly to replace the description field with a form function replaceDescription() { var d = document.getElementById("description"); - $(d).down().next().innerHTML = "
loading...
"; + $(d).down().next().innerHTML = "
"; new Ajax.Request( "./descriptionForm", { From 3e8afc53de7824a8d6fc113dff655aeb7174c55f Mon Sep 17 00:00:00 2001 From: Basil Crow Date: Fri, 5 Nov 2021 00:16:15 -0700 Subject: [PATCH 40/44] `surefire.runOrder=alphabetical` for reproducibility (#5857) --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index 69b840f38a8f..9867ea55c09e 100644 --- a/pom.xml +++ b/pom.xml @@ -260,6 +260,7 @@ THE SOFTWARE. true false + alphabetical From 935612d5b1306ab7f58a0673e296a01354f353fd Mon Sep 17 00:00:00 2001 From: Basil Crow Date: Fri, 5 Nov 2021 00:17:37 -0700 Subject: [PATCH 41/44] Unfork `AntClassLoader` (#5856) Co-authored-by: Tim Jacomb --- .../java/jenkins/util/AntClassLoader.java | 1627 +---------------- 1 file changed, 13 insertions(+), 1614 deletions(-) diff --git a/core/src/main/java/jenkins/util/AntClassLoader.java b/core/src/main/java/jenkins/util/AntClassLoader.java index 28b0dea743b2..9d302becd364 100644 --- a/core/src/main/java/jenkins/util/AntClassLoader.java +++ b/core/src/main/java/jenkins/util/AntClassLoader.java @@ -1,513 +1,45 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ package jenkins.util; -import java.io.ByteArrayOutputStream; -import java.io.Closeable; import java.io.File; import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.Constructor; -import java.net.MalformedURLException; import java.net.URL; -import java.nio.file.Files; -import java.security.CodeSource; -import java.security.ProtectionDomain; -import java.security.cert.Certificate; import java.util.Collection; -import java.util.Collections; import java.util.Enumeration; -import java.util.HashMap; -import java.util.Hashtable; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Objects; -import java.util.StringTokenizer; -import java.util.Vector; -import java.util.jar.Attributes; -import java.util.jar.Attributes.Name; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import java.util.jar.Manifest; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import java.util.zip.ZipFile; -import org.apache.tools.ant.BuildEvent; -import org.apache.tools.ant.BuildException; -import org.apache.tools.ant.MagicNames; +import jenkins.ClassLoaderReflectionToolkit; import org.apache.tools.ant.Project; -import org.apache.tools.ant.SubBuildListener; -import org.apache.tools.ant.launch.Locator; import org.apache.tools.ant.types.Path; -import org.apache.tools.ant.util.FileUtils; -import org.apache.tools.ant.util.JavaEnvUtils; -import org.apache.tools.ant.util.LoaderUtils; -import org.apache.tools.ant.util.ReflectUtil; -import org.apache.tools.ant.util.StringUtils; -import org.apache.tools.ant.util.VectorSet; -import org.apache.tools.zip.ZipLong; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; /** - * Used to load classes within ant with a different classpath from - * that used to start ant. Note that it is possible to force a class - * into this loader even when that class is on the system classpath by - * using the forceLoadClass method. Any subsequent classes loaded by that - * class will then use this loader rather than the system class loader. - * - *

- * Note that this classloader has a feature to allow loading - * in reverse order and for "isolation". - * Due to the fact that a number of - * methods in java.lang.ClassLoader are final (at least - * in java 1.4 getResources) this means that the - * class has to fake the given parent. - *

- * + * {@link org.apache.tools.ant.AntClassLoader} with loosened visibility for use with {@link + * ClassLoaderReflectionToolkit}. */ @Restricted(NoExternalUse.class) -public class AntClassLoader extends ClassLoader implements JenkinsClassLoader, SubBuildListener, Closeable { - - private static final FileUtils FILE_UTILS = FileUtils.getFileUtils(); - - private static final boolean IS_ATLEAST_JAVA9 = JavaEnvUtils.isAtLeastJavaVersion(JavaEnvUtils.JAVA_9); - // constructs needed to create (via reflection) a java.util.jar.JarFile instance when Java runtime version is >= 9 - private static final Class[] MR_JARFILE_CTOR_ARGS; - private static final Object MR_JARFILE_CTOR_RUNTIME_VERSION_VAL; - - static { - if (IS_ATLEAST_JAVA9) { - Class[] ctorArgs = null; - Object runtimeVersionVal = null; - try { - final Class runtimeVersionClass = Class.forName("java.lang.Runtime$Version"); - ctorArgs = new Class[] {File.class, boolean.class, int.class, runtimeVersionClass}; - runtimeVersionVal = Runtime.class.getDeclaredMethod("version").invoke(null); - } catch (Exception e) { - // ignore - we consider this as multi-release jar unsupported - } - MR_JARFILE_CTOR_ARGS = ctorArgs; - MR_JARFILE_CTOR_RUNTIME_VERSION_VAL = runtimeVersionVal; - } else { - MR_JARFILE_CTOR_ARGS = null; - MR_JARFILE_CTOR_RUNTIME_VERSION_VAL = null; - } - } - - /** - * An enumeration of all resources of a given name found within the - * classpath of this class loader. This enumeration is used by the - * ClassLoader.findResources method, which is in - * turn used by the ClassLoader.getResources method. - * - * @see AntClassLoader#findResources(String) - * @see java.lang.ClassLoader#getResources(String) - */ - private class ResourceEnumeration implements Enumeration { - /** - * The name of the resource being searched for. - */ - private final String resourceName; - - /** - * The index of the next classpath element to search. - */ - private int pathElementsIndex; - - /** - * The URL of the next resource to return in the enumeration. If this - * field is {@code null} then the enumeration has been completed, - * i.e., there are no more elements to return. - */ - private URL nextResource; - - /** - * Constructs a new enumeration of resources of the given name found - * within this class loader's classpath. - * - * @param name the name of the resource to search for. - */ - ResourceEnumeration(final String name) { - this.resourceName = name; - this.pathElementsIndex = 0; - findNextResource(); - } - - /** - * Indicates whether there are more elements in the enumeration to - * return. - * - * @return {@code true} if there are more elements in the - * enumeration; {@code false} otherwise. - */ - @Override - public boolean hasMoreElements() { - return this.nextResource != null; - } - - /** - * Returns the next resource in the enumeration. - * - * @return the next resource in the enumeration - */ - @Override - public URL nextElement() { - final URL ret = this.nextResource; - if (ret == null) { - throw new NoSuchElementException(); - } - findNextResource(); - return ret; - } - - /** - * Locates the next resource of the correct name in the classpath and - * sets {@code nextResource} to the URL of that resource. If no - * more resources can be found, {@code nextResource} is set to - * {@code null}. - */ - private void findNextResource() { - URL url = null; - while (pathElementsIndex < pathComponents.size() && url == null) { - try { - final File pathComponent = pathComponents.elementAt(pathElementsIndex); - url = getResourceURL(pathComponent, this.resourceName); - pathElementsIndex++; - } catch (final BuildException e) { - // ignore path elements which are not valid relative to the - // project - } - } - this.nextResource = url; - } - } - - /** - * The size of buffers to be used in this classloader. - */ - private static final int BUFFER_SIZE = 8192; - - /** - * Number of array elements in a test array of strings - */ - private static final int NUMBER_OF_STRINGS = 256; - - /** - * The components of the classpath that the classloader searches - * for classes. - */ - private final Vector pathComponents = new VectorSet<>(); - - /** - * The project to which this class loader belongs. - */ - private Project project; - - /** - * Indicates whether the parent class loader should be - * consulted before trying to load with this class loader. - */ - private boolean parentFirst = true; - - /** - * These are the package roots that are to be loaded by the parent class - * loader regardless of whether the parent class loader is being searched - * first or not. - */ - private final Vector systemPackages = new Vector<>(); - - /** - * These are the package roots that are to be loaded by this class loader - * regardless of whether the parent class loader is being searched first - * or not. - */ - private final Vector loaderPackages = new Vector<>(); - - /** - * Whether or not this classloader will ignore the base - * classloader if it can't find a class. - * - * @see #setIsolated(boolean) - */ - private boolean ignoreBase = false; - - /** - * The parent class loader, if one is given or can be determined. - */ - private ClassLoader parent = null; - - /** - * A hashtable of zip files opened by the classloader (File to JarFile). - */ - private Hashtable jarFiles = new Hashtable<>(); - - /** Static map of jar file/time to manifest class-path entries */ - private static Map pathMap = - Collections.synchronizedMap(new HashMap<>()); - - /** - * The context loader saved when setting the thread's current - * context loader. - */ - private ClassLoader savedContextLoader = null; +public class AntClassLoader extends org.apache.tools.ant.AntClassLoader implements JenkinsClassLoader { - /** - * Whether or not the context loader is currently saved. - */ - private boolean isContextLoaderSaved = false; - - /** - * Create an Ant ClassLoader for a given project, with - * a parent classloader and an initial classpath. - * @since Ant 1.7. - * @param parent the parent for this classloader. - * @param project The project to which this classloader is to - * belong. - * @param classpath The classpath to use to load classes. - */ public AntClassLoader(final ClassLoader parent, final Project project, final Path classpath) { - setParent(parent); - setClassPath(classpath); - setProject(project); + super(parent, project, classpath); } - /** - * Create an Ant Class Loader - */ - public AntClassLoader() { - setParent(null); - } + public AntClassLoader() {} - /** - * Creates a classloader for the given project using the classpath given. - * - * @param project The project to which this classloader is to belong. - * Must not be {@code null}. - * @param classpath The classpath to use to load the classes. This - * is combined with the system classpath in a manner - * determined by the value of ${build.sysclasspath}. - * May be {@code null}, in which case no path - * elements are set up to start with. - */ public AntClassLoader(final Project project, final Path classpath) { - setParent(null); - setProject(project); - setClassPath(classpath); + super(project, classpath); } - /** - * Creates a classloader for the given project using the classpath given. - * - * @param parent The parent classloader to which unsatisfied loading - * attempts are delegated. May be {@code null}, - * in which case the classloader which loaded this - * class is used as the parent. - * @param project The project to which this classloader is to belong. - * Must not be {@code null}. - * @param classpath the classpath to use to load the classes. - * May be {@code null}, in which case no path - * elements are set up to start with. - * @param parentFirst If {@code true}, indicates that the parent - * classloader should be consulted before trying to - * load the a class through this loader. - */ public AntClassLoader(final ClassLoader parent, final Project project, final Path classpath, final boolean parentFirst) { - this(project, classpath); - if (parent != null) { - setParent(parent); - } - setParentFirst(parentFirst); - addJavaLibraries(); + super(parent, project, classpath, parentFirst); } - /** - * Creates a classloader for the given project using the classpath given. - * - * @param project The project to which this classloader is to belong. - * Must not be {@code null}. - * @param classpath The classpath to use to load the classes. May be - * {@code null}, in which case no path - * elements are set up to start with. - * @param parentFirst If {@code true}, indicates that the parent - * classloader should be consulted before trying to - * load the a class through this loader. - */ public AntClassLoader(final Project project, final Path classpath, final boolean parentFirst) { - this(null, project, classpath, parentFirst); + super(project, classpath, parentFirst); } - /** - * Creates an empty class loader. The classloader should be configured - * with path elements to specify where the loader is to look for - * classes. - * - * @param parent The parent classloader to which unsatisfied loading - * attempts are delegated. May be {@code null}, - * in which case the classloader which loaded this - * class is used as the parent. - * @param parentFirst If {@code true}, indicates that the parent - * classloader should be consulted before trying to - * load the a class through this loader. - */ public AntClassLoader(final ClassLoader parent, final boolean parentFirst) { - setParent(parent); - project = null; - this.parentFirst = parentFirst; - } - - /** - * Set the project associated with this class loader - * - * @param project the project instance - */ - public void setProject(final Project project) { - this.project = project; - if (project != null) { - project.addBuildListener(this); - } - } - - /** - * Set the classpath to search for classes to load. This should not be - * changed once the classloader starts to server classes - * - * @param classpath the search classpath consisting of directories and - * jar/zip files. - */ - public void setClassPath(final Path classpath) { - pathComponents.removeAllElements(); - if (classpath != null) { - for (String pathElement : classpath.concatSystemClasspath("ignore").list()) { - try { - addPathElement(pathElement); - } catch (final BuildException e) { - // ignore path elements which are invalid - // relative to the project - log("Ignoring path element " + pathElement + " from " + - "classpath due to exception " + e, Project.MSG_DEBUG); - } - } - } - } - - /** - * Set the parent for this class loader. This is the class loader to which - * this class loader will delegate to load classes - * - * @param parent the parent class loader. - */ - public void setParent(final ClassLoader parent) { - this.parent = parent == null ? AntClassLoader.class.getClassLoader() : parent; - } - - /** - * Control whether class lookup is delegated to the parent loader first - * or after this loader. Use with extreme caution. Setting this to - * false violates the class loader hierarchy and can lead to Linkage errors - * - * @param parentFirst if true, delegate initial class search to the parent - * classloader. - */ - public void setParentFirst(final boolean parentFirst) { - this.parentFirst = parentFirst; - } - - /** - * Logs a message through the project object if one has been provided. - * - * @param message The message to log. - * Should not be {@code null}. - * - * @param priority The logging priority of the message. - */ - protected void log(final String message, final int priority) { - if (project != null) { - project.log(message, priority); - } else if (priority < Project.MSG_INFO) { - System.err.println(message); - } - } - - /** - * Sets the current thread's context loader to this classloader, storing - * the current loader value for later resetting. - */ - public void setThreadContextLoader() { - if (isContextLoaderSaved) { - throw new BuildException("Context loader has not been reset"); - } - if (LoaderUtils.isContextLoaderAvailable()) { - savedContextLoader = LoaderUtils.getContextClassLoader(); - ClassLoader loader = this; - if (project != null && "only".equals(project.getProperty(MagicNames.BUILD_SYSCLASSPATH))) { - loader = this.getClass().getClassLoader(); - } - LoaderUtils.setContextClassLoader(loader); - isContextLoaderSaved = true; - } - } - - /** - * Resets the current thread's context loader to its original value. - */ - public void resetThreadContextLoader() { - if (LoaderUtils.isContextLoaderAvailable() && isContextLoaderSaved) { - LoaderUtils.setContextClassLoader(savedContextLoader); - savedContextLoader = null; - isContextLoaderSaved = false; - } - } - - - /** - * Adds an element to the classpath to be searched. - * - * @param pathElement The path element to add. Must not be - * {@code null}. - * - * @exception BuildException if the given path element cannot be resolved - * against the project. - */ - public void addPathElement(final String pathElement) throws BuildException { - final File pathComponent = project != null ? project.resolveFile(pathElement) : new File( - pathElement); - try { - addPathFile(pathComponent); - } catch (final IOException e) { - throw new BuildException(e); - } - } - - /** - * Add a path component. - * This simply adds the file, unlike addPathElement - * it does not open jar files and load files from - * their CLASSPATH entry in the manifest file. - * @param file the jar file or directory to add. - */ - public void addPathComponent(final File file) { - if (pathComponents.contains(file)) { - return; - } - pathComponents.addElement(file); + super(parent, parentFirst); } public void addPathFiles(Collection paths) throws IOException { @@ -516,1156 +48,23 @@ public void addPathFiles(Collection paths) throws IOException { } } - /** - * Add a file to the path. - * Reads the manifest, if available, and adds any additional class path jars - * specified in the manifest. - * - * @param pathComponent the file which is to be added to the path for - * this class loader - * - * @throws IOException if data needed from the file cannot be read. - */ - protected void addPathFile(final File pathComponent) throws IOException { - if (!pathComponents.contains(pathComponent)) { - pathComponents.addElement(pathComponent); - } - if (pathComponent.isDirectory()) { - return; - } - - final String absPathPlusTimeAndLength = pathComponent.getAbsolutePath() - + pathComponent.lastModified() + "-" + pathComponent.length(); - String classpath = pathMap.get(absPathPlusTimeAndLength); - if (classpath == null) { - try (JarFile jarFile = newJarFile(pathComponent)) { - final Manifest manifest = jarFile.getManifest(); - if (manifest == null) { - return; - } - classpath = manifest.getMainAttributes() - .getValue(Attributes.Name.CLASS_PATH); - } - if (classpath == null) { - classpath = ""; - } - pathMap.put(absPathPlusTimeAndLength, classpath); - } - - if (!classpath.isEmpty()) { - final URL baseURL = FILE_UTILS.getFileURL(pathComponent); - final StringTokenizer st = new StringTokenizer(classpath); - while (st.hasMoreTokens()) { - final String classpathElement = st.nextToken(); - final URL libraryURL = new URL(baseURL, classpathElement); - if (!libraryURL.getProtocol().equals("file")) { - log("Skipping jar library " + classpathElement - + " since only relative URLs are supported by this" + " loader", - Project.MSG_VERBOSE); - continue; - } - final String decodedPath = Locator.decodeUri(libraryURL.getFile()); - final File libraryFile = new File(decodedPath); - if (libraryFile.exists() && !isInPath(libraryFile)) { - addPathFile(libraryFile); - } - } - } - } - - /** - * Returns the classpath this classloader will consult. - * - * @return the classpath used for this classloader, with elements - * separated by the path separator for the system. - */ - public String getClasspath() { - final StringBuilder sb = new StringBuilder(); - for (final File component : pathComponents) { - if (sb.length() > 0) { - sb.append(File.pathSeparator); - } - sb.append(component.getAbsolutePath()); - } - return sb.toString(); - } - - /** - * Sets whether this classloader should run in isolated mode. In - * isolated mode, classes not found on the given classpath will - * not be referred to the parent class loader but will cause a - * ClassNotFoundException. - * - * @param isolated Whether or not this classloader should run in - * isolated mode. - */ - public synchronized void setIsolated(final boolean isolated) { - ignoreBase = isolated; - } - - /** - * Forces initialization of a class in a JDK 1.1 compatible, albeit hacky - * way. - * - * @param theClass The class to initialize. - * Must not be {@code null}. - * - * @deprecated since 1.6.x. - * Use Class.forName with initialize=true instead. - */ - @Deprecated - public static void initializeClass(final Class theClass) { - // ***HACK*** We ask the VM to create an instance - // by voluntarily providing illegal arguments to force - // the VM to run the class' static initializer, while - // at the same time not running a valid constructor. - - final Constructor[] cons = theClass.getDeclaredConstructors(); - //At least one constructor is guaranteed to be there, but check anyway. - if (cons != null) { - if (cons.length > 0 && cons[0] != null) { - final String[] strs = new String[NUMBER_OF_STRINGS]; - try { - cons[0].newInstance((Object[]) strs); - // Expecting an exception to be thrown by this call: - // IllegalArgumentException: wrong number of Arguments - } catch (final Exception e) { - // Ignore - we are interested only in the side - // effect - that of getting the static initializers - // invoked. As we do not want to call a valid - // constructor to get this side effect, an - // attempt is made to call a hopefully - // invalid constructor - come on, nobody - // would have a constructor that takes in - // 256 String arguments ;-) - // (In fact, they can't - according to JVM spec - // section 4.10, the number of method parameters is limited - // to 255 by the definition of a method descriptor. - // Constructors count as methods here.) - } - } - } - } - - /** - * Adds a package root to the list of packages which must be loaded on the - * parent loader. - * - * All subpackages are also included. - * - * @param packageRoot The root of all packages to be included. - * Should not be {@code null}. - */ - public void addSystemPackageRoot(final String packageRoot) { - systemPackages.addElement(packageRoot + (packageRoot.endsWith(".") ? "" : ".")); - } - - /** - * Adds a package root to the list of packages which must be loaded using - * this loader. - * - * All subpackages are also included. - * - * @param packageRoot The root of all packages to be included. - * Should not be {@code null}. - */ - public void addLoaderPackageRoot(final String packageRoot) { - loaderPackages.addElement(packageRoot + (packageRoot.endsWith(".") ? "" : ".")); - } - - /** - * Loads a class through this class loader even if that class is available - * on the parent classpath. - * - * This ensures that any classes which are loaded by the returned class - * will use this classloader. - * - * @param classname The name of the class to be loaded. - * Must not be {@code null}. - * - * @return the required Class object - * - * @exception ClassNotFoundException if the requested class does not exist - * on this loader's classpath. - */ - public Class forceLoadClass(final String classname) throws ClassNotFoundException { - log("force loading " + classname, Project.MSG_DEBUG); - - Class theClass = findLoadedClass(classname); - - if (theClass == null) { - theClass = findClass(classname); - } - return theClass; - } - - /** - * Loads a class through this class loader but defer to the parent class - * loader. - * - * This ensures that instances of the returned class will be compatible - * with instances which have already been loaded on the parent - * loader. - * - * @param classname The name of the class to be loaded. - * Must not be {@code null}. - * - * @return the required Class object - * - * @exception ClassNotFoundException if the requested class does not exist - * on this loader's classpath. - */ - public Class forceLoadSystemClass(final String classname) throws ClassNotFoundException { - log("force system loading " + classname, Project.MSG_DEBUG); - - Class theClass = findLoadedClass(classname); - - if (theClass == null) { - theClass = findBaseClass(classname); - } - return theClass; - } - - /** - * Returns a stream to read the requested resource name. - * - * @param name The name of the resource for which a stream is required. - * Must not be {@code null}. - * - * @return a stream to the required resource or {@code null} if the - * resource cannot be found on the loader's classpath. - */ - @Override - public InputStream getResourceAsStream(final String name) { - InputStream resourceStream = null; - if (isParentFirst(name)) { - resourceStream = loadBaseResource(name); - } - if (resourceStream != null) { - log("ResourceStream for " + name - + " loaded from parent loader", Project.MSG_DEBUG); - } else { - resourceStream = loadResource(name); - if (resourceStream != null) { - log("ResourceStream for " + name - + " loaded from ant loader", Project.MSG_DEBUG); - } - } - if (resourceStream == null && !isParentFirst(name)) { - if (ignoreBase) { - resourceStream = getRootLoader() == null - ? null - : getRootLoader().getResourceAsStream(name); - } else { - resourceStream = loadBaseResource(name); - } - if (resourceStream != null) { - log("ResourceStream for " + name + " loaded from parent loader", - Project.MSG_DEBUG); - } - } - if (resourceStream == null) { - log("Couldn't load ResourceStream for " + name, Project.MSG_DEBUG); - } - return resourceStream; - } - - /** - * Returns a stream to read the requested resource name from this loader. - * - * @param name The name of the resource for which a stream is required. - * Must not be {@code null}. - * - * @return a stream to the required resource or {@code null} if - * the resource cannot be found on the loader's classpath. - */ - private InputStream loadResource(final String name) { - // we need to search the components of the path to see if we can - // find the class we want. - return pathComponents.stream().map(path -> getResourceStream(path, name)) - .filter(Objects::nonNull).findFirst().orElse(null); - } - - /** - * Finds a system resource (which should be loaded from the parent - * classloader). - * - * @param name The name of the system resource to load. - * Must not be {@code null}. - * - * @return a stream to the named resource, or {@code null} if - * the resource cannot be found. - */ - private InputStream loadBaseResource(final String name) { - return parent == null ? super.getResourceAsStream(name) : parent.getResourceAsStream(name); - } - - /** - * Returns an inputstream to a given resource in the given file which may - * either be a directory or a zip file. - * - * @param file the file (directory or jar) in which to search for the - * resource. Must not be {@code null}. - * @param resourceName The name of the resource for which a stream is - * required. Must not be {@code null}. - * - * @return a stream to the required resource or {@code null} if - * the resource cannot be found in the given file. - */ - private InputStream getResourceStream(final File file, final String resourceName) { - try { - JarFile jarFile = jarFiles.get(file); - if (jarFile == null && file.isDirectory()) { - final File resource = new File(file, resourceName); - if (resource.exists()) { - return Files.newInputStream(resource.toPath()); - } - } else { - if (jarFile == null) { - if (file.exists()) { - jarFile = newJarFile(file); - jarFiles.put(file, jarFile); - } else { - return null; - } - //to eliminate a race condition, retrieve the entry - //that is in the hash table under that filename - jarFile = jarFiles.get(file); - } - final JarEntry entry = jarFile.getJarEntry(resourceName); - if (entry != null) { - return jarFile.getInputStream(entry); - } - } - } catch (final Exception e) { - log("Ignoring Exception " + e.getClass().getName() + ": " + e.getMessage() - + " reading resource " + resourceName + " from " + file, Project.MSG_VERBOSE); - } - return null; - } - - /** - * Tests whether or not the parent classloader should be checked for a - * resource before this one. If the resource matches both the "use parent - * classloader first" and the "use this classloader first" lists, the latter - * takes priority. - * - * @param resourceName - * The name of the resource to check. Must not be - * {@code null}. - * - * @return whether or not the parent classloader should be checked for a - * resource before this one is. - */ - private boolean isParentFirst(final String resourceName) { - // default to the global setting and then see - // if this class belongs to a package which has been - // designated to use a specific loader first - // (this one or the parent one) - - // TODO - shouldn't this always return false in isolated mode? - - return loaderPackages.stream().noneMatch(resourceName::startsWith) - && (systemPackages.stream().anyMatch(resourceName::startsWith) || parentFirst); - } - - /** - * Used for isolated resource searching. - * @return the root classloader of AntClassLoader. - */ - private ClassLoader getRootLoader() { - ClassLoader ret = getClass().getClassLoader(); - while (ret != null && ret.getParent() != null) { - ret = ret.getParent(); - } - return ret; - } - - /** - * Finds the resource with the given name. A resource is - * some data (images, audio, text, etc) that can be accessed by class - * code in a way that is independent of the location of the code. - * - * @param name The name of the resource for which a stream is required. - * Must not be {@code null}. - * - * @return a URL for reading the resource, or {@code null} if the - * resource could not be found or the caller doesn't have - * adequate privileges to get the resource. - */ - @Override - public URL getResource(final String name) { - // we need to search the components of the path to see if - // we can find the class we want. - URL url = null; - if (isParentFirst(name)) { - url = parent == null ? super.getResource(name) : parent.getResource(name); - } - if (url != null) { - log("Resource " + name + " loaded from parent loader", Project.MSG_DEBUG); - } else { - url = getUrl(name); - } - if (url == null && !isParentFirst(name)) { - // this loader was first but it didn't find it - try the parent - if (ignoreBase) { - url = getRootLoader() == null ? null : getRootLoader().getResource(name); - } else { - url = parent == null ? super.getResource(name) : parent.getResource(name); - } - if (url != null) { - log("Resource " + name + " loaded from parent loader", Project.MSG_DEBUG); - } - } - if (url == null) { - log("Couldn't load Resource " + name, Project.MSG_DEBUG); - } - return url; - } - - /** - * Finds a matching file by iterating through path components. - * - * @param name File to find - * @return A {@code URL} object for reading the resource, or {@code null} if the - * resource could not be found - */ - private URL getUrl(String name) { - URL url = null; - - // try and load from this loader if the parent either didn't find - // it or wasn't consulted. - for (final File pathComponent : pathComponents) { - url = getResourceURL(pathComponent, name); - if (url != null) { - log("Resource " + name + " loaded from ant loader", Project.MSG_DEBUG); - break; - } - } - - return url; - } - - /** - * Finds all the resources with the given name. A resource is some - * data (images, audio, text, etc) that can be accessed by class - * code in a way that is independent of the location of the code. - * - * @param name name of the resource - * @return possible URLs as enumeration - * @throws IOException if something goes wrong - * @see #findResources(String, boolean) - * @since Ant 1.8.0 - */ - public Enumeration getNamedResources(final String name) - throws IOException { - return findResources(name, false); - } - - /** - * Finds the resource with the given name. - * - * @param name The resource name - * @return A {@code URL} object for reading the resource, or {@code null} if the - * resource could not be found - */ @Override public URL findResource(final String name) { - return getUrl(name); + return super.findResource(name); } - /** - * Returns an enumeration of URLs representing all the resources with the - * given name by searching the class loader's classpath. - * - * @param name The resource name to search for. - * Must not be {@code null}. - * @return an enumeration of URLs for the resources - * @exception IOException if I/O errors occurs (can't happen) - */ @Override public Enumeration findResources(final String name) throws IOException { - return findResources(name, true); - } - - /** - * Returns an enumeration of URLs representing all the resources with the - * given name by searching the class loader's classpath. - * - * @param name The resource name to search for. - * Must not be {@code null}. - * @param skipParent whether to skip searching the parent first - will be false if the method is - * invoked from {@link #getResources(String)} or {@link #getNamedResources(String)} and true - * if the method is invoked from {@link #findResources(String)}. - * @return an enumeration of URLs for the resources - * @exception IOException if I/O errors occurs (can't happen) - */ - protected Enumeration findResources(final String name, - final boolean skipParent) - throws IOException { - final Enumeration mine = new ResourceEnumeration(name); - Enumeration base; - if (parent != null && !skipParent) { - // Delegate to the parent: - base = parent.getResources(name); - } else { - base = Collections.emptyEnumeration(); - } - if (isParentFirst(name)) { - // Normal case. - return append(base, mine); - } - if (ignoreBase) { - return getRootLoader() == null ? mine - : append(mine, getRootLoader().getResources(name)); - } - // parent last: - return append(mine, base); - } - - private static Enumeration append(Enumeration one, Enumeration two) { - return Stream.concat(Collections.list(one).stream(), Collections.list(two).stream()) - .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::enumeration)); - } - - /** - * Returns the URL of a given resource in the given file which may - * either be a directory or a zip file. - * - * @param file The file (directory or jar) in which to search for - * the resource. Must not be {@code null}. - * @param resourceName The name of the resource for which a stream - * is required. Must not be {@code null}. - * - * @return a stream to the required resource or {@code null} if the - * resource cannot be found in the given file object. - */ - protected URL getResourceURL(final File file, final String resourceName) { - try { - JarFile jarFile = jarFiles.get(file); - if (jarFile == null && file.isDirectory()) { - final File resource = new File(file, resourceName); - - if (resource.exists()) { - try { - return FILE_UTILS.getFileURL(resource); - } catch (final MalformedURLException ex) { - return null; - } - } - } else { - if (jarFile == null) { - if (file.exists()) { - if (!isZip(file)) { - final String msg = "CLASSPATH element " + file - + " is not a JAR."; - log(msg, Project.MSG_WARN); - return null; - } - jarFile = newJarFile(file); - jarFiles.put(file, jarFile); - } else { - return null; - } - // potential race-condition - jarFile = jarFiles.get(file); - } - final JarEntry entry = jarFile.getJarEntry(resourceName); - if (entry != null) { - try { - return new URL("jar:" + FILE_UTILS.getFileURL(file) + "!/" + entry); - } catch (final MalformedURLException ex) { - return null; - } - } - } - } catch (final Exception e) { - final String msg = "Unable to obtain resource from " + file + ": "; - log(msg + e, Project.MSG_WARN); - log(StringUtils.getStackTrace(e), Project.MSG_WARN); - } - return null; - } - - /** - * Loads a class with this class loader. - * - * This class attempts to load the class in an order determined by whether - * or not the class matches the system/loader package lists, with the - * loader package list taking priority. If the classloader is in isolated - * mode, failure to load the class in this loader will result in a - * ClassNotFoundException. - * - * @param classname The name of the class to be loaded. - * Must not be {@code null}. - * @param resolve {@code true} if all classes upon which this class - * depends are to be loaded. - * - * @return the required Class object - * - * @exception ClassNotFoundException if the requested class does not exist - * on the system classpath (when not in isolated mode) or this loader's - * classpath. - */ - @Override - protected synchronized Class loadClass(final String classname, final boolean resolve) - throws ClassNotFoundException { - // 'sync' is needed - otherwise 2 threads can load the same class - // twice, resulting in LinkageError: duplicated class definition. - // findLoadedClass avoids that, but without sync it won't work. - - Class theClass = findLoadedClass(classname); - if (theClass != null) { - return theClass; - } - if (isParentFirst(classname)) { - try { - theClass = findBaseClass(classname); - log("Class " + classname + " loaded from parent loader " + "(parentFirst)", - Project.MSG_DEBUG); - } catch (final ClassNotFoundException cnfe) { - theClass = findClass(classname); - log("Class " + classname + " loaded from ant loader " + "(parentFirst)", - Project.MSG_DEBUG); - } - } else { - try { - theClass = findClass(classname); - log("Class " + classname + " loaded from ant loader", Project.MSG_DEBUG); - } catch (final ClassNotFoundException cnfe) { - if (ignoreBase) { - throw cnfe; - } - theClass = findBaseClass(classname); - log("Class " + classname + " loaded from parent loader", Project.MSG_DEBUG); - } - } - if (resolve) { - resolveClass(theClass); - } - return theClass; - } - - /** - * Converts the class dot notation to a filesystem equivalent for - * searching purposes. - * - * @param classname The class name in dot format (eg java.lang.Integer). - * Must not be {@code null}. - * - * @return the classname in filesystem format (eg java/lang/Integer.class) - */ - private String getClassFilename(final String classname) { - return classname.replace('.', '/') + ".class"; - } - - /** - * Define a class given its bytes - * - * @param container the container from which the class data has been read - * may be a directory or a jar/zip file. - * - * @param classData the bytecode data for the class - * @param classname the name of the class - * - * @return the Class instance created from the given data - * - * @throws IOException if the class data cannot be read. - */ - protected Class defineClassFromData(final File container, final byte[] classData, final String classname) - throws IOException { - definePackage(container, classname); - final ProtectionDomain currentPd = Project.class.getProtectionDomain(); - final String classResource = getClassFilename(classname); - final CodeSource src = new CodeSource(FILE_UTILS.getFileURL(container), - getCertificates(container, - classResource)); - final ProtectionDomain classesPd = - new ProtectionDomain(src, currentPd.getPermissions(), - this, - currentPd.getPrincipals()); - return defineClass(classname, classData, 0, classData.length, - classesPd); - } - - /** - * Define the package information associated with a class. - * - * @param container the file containing the class definition. - * @param className the class name of for which the package information - * is to be determined. - * - * @exception IOException if the package information cannot be read from the - * container. - */ - protected void definePackage(final File container, final String className) throws IOException { - final int classIndex = className.lastIndexOf('.'); - if (classIndex == -1) { - return; - } - final String packageName = className.substring(0, classIndex); - if (getPackage(packageName) != null) { - // already defined - return; - } - // define the package now - final Manifest manifest = getJarManifest(container); - - if (manifest == null) { - definePackage(packageName, null, null, null, null, null, null, null); - } else { - definePackage(container, packageName, manifest); - } - } - - /** - * Get the manifest from the given jar, if it is indeed a jar and it has a - * manifest - * - * @param container the File from which a manifest is required. - * - * @return the jar's manifest or null is the container is not a jar or it - * has no manifest. - * - * @exception IOException if the manifest cannot be read. - */ - private Manifest getJarManifest(final File container) throws IOException { - if (container.isDirectory()) { - return null; - } - final JarFile jarFile = jarFiles.get(container); - if (jarFile == null) { - return null; - } - return jarFile.getManifest(); - } - - /** - * Get the certificates for a given jar entry, if it is indeed a jar. - * - * @param container the File from which to read the entry - * @param entry the entry of which the certificates are requested - * - * @return the entry's certificates or null is the container is - * not a jar or it has no certificates. - */ - private Certificate[] getCertificates(final File container, final String entry) { - if (container.isDirectory()) { - return null; - } - final JarFile jarFile = jarFiles.get(container); - if (jarFile == null) { - return null; - } - final JarEntry ent = jarFile.getJarEntry(entry); - return ent == null ? null : ent.getCertificates(); - } - - /** - * Define the package information when the class comes from a - * jar with a manifest - * - * @param container the jar file containing the manifest - * @param packageName the name of the package being defined. - * @param manifest the jar's manifest - */ - protected void definePackage(final File container, final String packageName, final Manifest manifest) { - final String sectionName = packageName.replace('.', '/') + "/"; - - String specificationTitle = null; - String specificationVendor = null; - String specificationVersion = null; - String implementationTitle = null; - String implementationVendor = null; - String implementationVersion = null; - String sealedString = null; - URL sealBase = null; - - final Attributes sectionAttributes = manifest.getAttributes(sectionName); - if (sectionAttributes != null) { - specificationTitle = sectionAttributes.getValue(Name.SPECIFICATION_TITLE); - specificationVendor = sectionAttributes.getValue(Name.SPECIFICATION_VENDOR); - specificationVersion = sectionAttributes.getValue(Name.SPECIFICATION_VERSION); - implementationTitle = sectionAttributes.getValue(Name.IMPLEMENTATION_TITLE); - implementationVendor = sectionAttributes.getValue(Name.IMPLEMENTATION_VENDOR); - implementationVersion = sectionAttributes.getValue(Name.IMPLEMENTATION_VERSION); - sealedString = sectionAttributes.getValue(Name.SEALED); - } - final Attributes mainAttributes = manifest.getMainAttributes(); - if (mainAttributes != null) { - if (specificationTitle == null) { - specificationTitle = mainAttributes.getValue(Name.SPECIFICATION_TITLE); - } - if (specificationVendor == null) { - specificationVendor = mainAttributes.getValue(Name.SPECIFICATION_VENDOR); - } - if (specificationVersion == null) { - specificationVersion = mainAttributes.getValue(Name.SPECIFICATION_VERSION); - } - if (implementationTitle == null) { - implementationTitle = mainAttributes.getValue(Name.IMPLEMENTATION_TITLE); - } - if (implementationVendor == null) { - implementationVendor = mainAttributes.getValue(Name.IMPLEMENTATION_VENDOR); - } - if (implementationVersion == null) { - implementationVersion = mainAttributes.getValue(Name.IMPLEMENTATION_VERSION); - } - if (sealedString == null) { - sealedString = mainAttributes.getValue(Name.SEALED); - } - } - if (sealedString != null && sealedString.equalsIgnoreCase("true")) { - try { - sealBase = new URL(FileUtils.getFileUtils().toURI(container.getAbsolutePath())); - } catch (final MalformedURLException e) { - // ignore - } - } - definePackage(packageName, specificationTitle, specificationVersion, specificationVendor, - implementationTitle, implementationVersion, implementationVendor, sealBase); - } - - /** - * Reads a class definition from a stream. - * - * @param stream The stream from which the class is to be read. - * Must not be {@code null}. - * @param classname The name of the class in the stream. - * Must not be {@code null}. - * @param container the file or directory containing the class. - * - * @return the Class object read from the stream. - * - * @exception IOException if there is a problem reading the class from the - * stream. - * @exception SecurityException if there is a security problem while - * reading the class from the stream. - */ - private Class getClassFromStream(final InputStream stream, final String classname, final File container) - throws IOException, SecurityException { - final ByteArrayOutputStream baos = new ByteArrayOutputStream(); - int bytesRead = -1; - final byte[] buffer = new byte[BUFFER_SIZE]; - - while ((bytesRead = stream.read(buffer, 0, BUFFER_SIZE)) != -1) { - baos.write(buffer, 0, bytesRead); - } - final byte[] classData = baos.toByteArray(); - return defineClassFromData(container, classData, classname); - } - - /** - * Searches for and load a class on the classpath of this class loader. - * - * @param name The name of the class to be loaded. Must not be - * {@code null}. - * - * @return the required Class object - * - * @exception ClassNotFoundException if the requested class does not exist - * on this loader's classpath. - */ - @Override - public Class findClass(final String name) throws ClassNotFoundException { - log("Finding class " + name, Project.MSG_DEBUG); - return findClassInComponents(name); - } - - /** - * Indicate if the given file is in this loader's path - * - * @param component the file which is to be checked - * - * @return true if the file is in the class path - */ - protected boolean isInPath(final File component) { - return pathComponents.contains(component); - } - - /** - * Finds a class on the given classpath. - * - * @param name The name of the class to be loaded. Must not be - * {@code null}. - * - * @return the required Class object - * - * @exception ClassNotFoundException if the requested class does not exist - * on this loader's classpath. - */ - private Class findClassInComponents(final String name) - throws ClassNotFoundException { - // we need to search the components of the path to see if - // we can find the class we want. - final String classFilename = getClassFilename(name); - for (final File pathComponent : pathComponents) { - try (InputStream stream = getResourceStream(pathComponent, classFilename)) { - if (stream != null) { - log("Loaded from " + pathComponent + " " - + classFilename, Project.MSG_DEBUG); - return getClassFromStream(stream, name, pathComponent); - } - } catch (final SecurityException se) { - throw se; - } catch (final IOException ioe) { - // ioe.printStackTrace(); - log("Exception reading component " + pathComponent + " (reason: " - + ioe.getMessage() + ")", Project.MSG_VERBOSE); - } - } - throw new ClassNotFoundException(name); - } - - /** - * Finds a system class (which should be loaded from the same classloader - * as the Ant core). - * - * For JDK 1.1 compatibility, this uses the findSystemClass method if - * no parent classloader has been specified. - * - * @param name The name of the class to be loaded. - * Must not be {@code null}. - * - * @return the required Class object - * - * @exception ClassNotFoundException if the requested class does not exist - * on this loader's classpath. - */ - private Class findBaseClass(final String name) throws ClassNotFoundException { - return parent == null ? findSystemClass(name) : parent.loadClass(name); - } - - /** - * Cleans up any resources held by this classloader. Any open archive - * files are closed. - */ - public synchronized void cleanup() { - for (final JarFile jarFile : jarFiles.values()) { - FileUtils.close(jarFile); - } - jarFiles = new Hashtable<>(); - if (project != null) { - project.removeBuildListener(this); - } - project = null; - } - - /** - * Gets the parent as has been specified in the constructor or via - * setParent. - * - * @return classloader - * @since Ant 1.8.0 - */ - public ClassLoader getConfiguredParent() { - return parent; - } - - /** - * Empty implementation to satisfy the BuildListener interface. - * - * @param event the buildStarted event - */ - @Override - public void buildStarted(final BuildEvent event) { - // Not significant for the class loader. - } - - /** - * Cleans up any resources held by this classloader at the end - * of a build. - * - * @param event the buildFinished event - */ - @Override - public void buildFinished(final BuildEvent event) { - cleanup(); - } - - /** - * Cleans up any resources held by this classloader at the end of - * a subbuild if it has been created for the subbuild's project - * instance. - * - * @param event the buildFinished event - * - * @since Ant 1.6.2 - */ - @Override - public void subBuildFinished(final BuildEvent event) { - if (event.getProject() == project) { - cleanup(); - } - } - - /** - * Empty implementation to satisfy the BuildListener interface. - * - * @param event the buildStarted event - * - * @since Ant 1.6.2 - */ - @Override - public void subBuildStarted(final BuildEvent event) { - // Not significant for the class loader. - } - - /** - * Empty implementation to satisfy the BuildListener interface. - * - * @param event the targetStarted event - */ - @Override - public void targetStarted(final BuildEvent event) { - // Not significant for the class loader. - } - - /** - * Empty implementation to satisfy the BuildListener interface. - * - * @param event the targetFinished event - */ - @Override - public void targetFinished(final BuildEvent event) { - // Not significant for the class loader. - } - - /** - * Empty implementation to satisfy the BuildListener interface. - * - * @param event the taskStarted event - */ - @Override - public void taskStarted(final BuildEvent event) { - // Not significant for the class loader. - } - - /** - * Empty implementation to satisfy the BuildListener interface. - * - * @param event the taskFinished event - */ - @Override - public void taskFinished(final BuildEvent event) { - // Not significant for the class loader. + return super.findResources(name); } - /** - * Empty implementation to satisfy the BuildListener interface. - * - * @param event the messageLogged event - */ - @Override - public void messageLogged(final BuildEvent event) { - // Not significant for the class loader. - } - - /** - * add any libraries that come with different java versions - * here - */ - public void addJavaLibraries() { - JavaEnvUtils.getJrePackages().forEach(this::addSystemPackageRoot); - } - - /** - * Returns a {@code String} representing this loader. - * @return the path that this classloader has. - */ - @Override - public String toString() { - return "AntClassLoader[" + getClasspath() + "]"; - } - - /** - * Public version of {@link ClassLoader#findLoadedClass(String)} - */ @Override public Class findLoadedClass2(String name) { - return findLoadedClass(name); + return super.findLoadedClass(name); } - /** - * Public version of {@link ClassLoader#getClassLoadingLock(String)} - */ @Override public Object getClassLoadingLock(String className) { return super.getClassLoadingLock(className); } - - /** {@inheritDoc} */ - @Override - public Enumeration getResources(String name) throws IOException { - return getNamedResources(name); - } - - /** {@inheritDoc} */ - @Override - public void close() { - cleanup(); - } - - /** - * Factory method - * - * @param parent ClassLoader - * @param project Project - * @param path Path - * @param parentFirst boolean - * @return AntClassLoader - */ - public static AntClassLoader newAntClassLoader(final ClassLoader parent, - final Project project, - final Path path, - final boolean parentFirst) { - return new AntClassLoader(parent, project, path, parentFirst); - } - - private static final ZipLong EOCD_SIG = new ZipLong(0X06054B50L); - private static final ZipLong SINGLE_SEGMENT_SPLIT_MARKER = - new ZipLong(0X30304B50L); - - private static boolean isZip(final File file) throws IOException { - final byte[] sig = new byte[4]; - if (readFully(file, sig)) { - final ZipLong start = new ZipLong(sig); - return ZipLong.LFH_SIG.equals(start) // normal file - || EOCD_SIG.equals(start) // empty zip - || ZipLong.DD_SIG.equals(start) // split zip - || SINGLE_SEGMENT_SPLIT_MARKER.equals(start); - } - return false; - } - - private static boolean readFully(final File f, final byte[] b) throws IOException { - try (InputStream fis = Files.newInputStream(f.toPath())) { - final int len = b.length; - int count = 0, x = 0; - while (count != len) { - x = fis.read(b, count, len - count); - if (x == -1) { - break; - } - count += x; - } - return count == len; - } - } - - /** - * - * @param file The file representing the jar - * @return Returns a {@link JarFile} instance, which is constructed based upon the Java runtime version. - * Depending on the Java runtime version, the returned instance may or may not be {@code multi-release} - * aware (a feature introduced in Java 9) - * @throws IOException - */ - private static JarFile newJarFile(final File file) throws IOException { - if (!IS_ATLEAST_JAVA9 || MR_JARFILE_CTOR_ARGS == null || MR_JARFILE_CTOR_RUNTIME_VERSION_VAL == null) { - return new JarFile(file); - } - return ReflectUtil.newInstance(JarFile.class, MR_JARFILE_CTOR_ARGS, - new Object[] {file, true, ZipFile.OPEN_READ, MR_JARFILE_CTOR_RUNTIME_VERSION_VAL}); - } } From 322aac3497575bccc4d9d0bfbb9930c89bf9ef11 Mon Sep 17 00:00:00 2001 From: Wei-Chin Huang <72090108+jamesD33064@users.noreply.github.com> Date: Fri, 5 Nov 2021 15:17:46 +0800 Subject: [PATCH 42/44] [JENKINS-66749] Missing spaces in upstream/downstream project view (#5850) Co-authored-by: Tim Jacomb --- core/src/main/resources/lib/hudson/buildCaption.jelly | 2 +- core/src/main/resources/lib/hudson/jobLink.jelly | 6 ++++-- war/src/main/less/base/style.less | 4 ++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/core/src/main/resources/lib/hudson/buildCaption.jelly b/core/src/main/resources/lib/hudson/buildCaption.jelly index feb3c139344e..8936ebe7bd73 100644 --- a/core/src/main/resources/lib/hudson/buildCaption.jelly +++ b/core/src/main/resources/lib/hudson/buildCaption.jelly @@ -71,7 +71,7 @@ THE SOFTWARE. })(); - + diff --git a/core/src/main/resources/lib/hudson/jobLink.jelly b/core/src/main/resources/lib/hudson/jobLink.jelly index 6c6a822ee2c3..0aaa0dcc7d5a 100644 --- a/core/src/main/resources/lib/hudson/jobLink.jelly +++ b/core/src/main/resources/lib/hudson/jobLink.jelly @@ -31,6 +31,8 @@ THE SOFTWARE. - - + + + + diff --git a/war/src/main/less/base/style.less b/war/src/main/less/base/style.less index 80d7e860f6f1..15c5c722cffb 100644 --- a/war/src/main/less/base/style.less +++ b/war/src/main/less/base/style.less @@ -1663,6 +1663,10 @@ svg.icon-xlg { fill: currentColor; } +.jenkins-icon-adjacent { + margin-left: 0.2rem; +} + /* -------------- Unclassified ---------- */ .spacer { From 2c1c5ee20ea887c0e2aecf94b68cba6e7107740c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 Nov 2021 07:24:35 +0000 Subject: [PATCH 43/44] Bump actions/checkout from 2.3.5 to 2.4.0 (#5877) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/changelog.yml | 2 +- .github/workflows/publish-release-artifact.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index 082238a21eea..1b60844219be 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -44,7 +44,7 @@ jobs: private_key: ${{ secrets.JENKINS_CHANGELOG_UPDATER_PRIVATE_KEY }} repository: jenkins-infra/jenkins.io - name: Check out - uses: actions/checkout@v2.3.5 + uses: actions/checkout@v2.4.0 with: fetch-depth: 0 - name: Publish jenkins.io changelog draft diff --git a/.github/workflows/publish-release-artifact.yml b/.github/workflows/publish-release-artifact.yml index dfabf3006b03..1d8277be3f2d 100644 --- a/.github/workflows/publish-release-artifact.yml +++ b/.github/workflows/publish-release-artifact.yml @@ -11,7 +11,7 @@ jobs: project-version: ${{ steps.set-version.outputs.project-version }} is-lts: ${{ steps.set-version.outputs.is-lts }} steps: - - uses: actions/checkout@v2.3.5 + - uses: actions/checkout@v2.4.0 - name: Set up JDK 8 uses: actions/setup-java@v2 with: From 5537928a1908897113023c45fd723a1f4cd9a923 Mon Sep 17 00:00:00 2001 From: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Fri, 5 Nov 2021 19:32:05 +0000 Subject: [PATCH 44/44] Init --- war/src/main/js/widgets/config/model/ConfigTableMetaData.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/war/src/main/js/widgets/config/model/ConfigTableMetaData.js b/war/src/main/js/widgets/config/model/ConfigTableMetaData.js index 4c35d0c92963..42cfbbad65dd 100644 --- a/war/src/main/js/widgets/config/model/ConfigTableMetaData.js +++ b/war/src/main/js/widgets/config/model/ConfigTableMetaData.js @@ -26,7 +26,7 @@ function closestTR(node) { function fromConfigTable(configTable) { var $ = getJQuery(); - var sectionHeaders = $('.section-header', configTable); + var sectionHeaders = $('.jenkins-section__header', configTable); var configForm = markConfigTableParentForm(configTable); // Mark the ancestor s of the section headers and add a title