diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ab12568a0a3b58e3b1789651e0d0f0d3343556df..4311a35ac21abf137bfc69b9415f3f62bb49f8d1 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -2,7 +2,7 @@
This page provides information about contributing code to the Jenkins core codebase.
-:exclamation: There's a lot more to the Jenkins project than just code. For information on contributing to the Jenkins project overall, check out https://jenkins.io/participate/.
+:exclamation: There's a lot more to the Jenkins project than just code. For information on contributing to the Jenkins project overall, check out [Participate].
## Getting started
@@ -10,32 +10,32 @@ This page provides information about contributing code to the Jenkins core codeb
2. Clone the forked repository to your machine
3. Install the development tools. In order to develop Jenkins, you need the following tools:
* Java Development Kit (JDK) 8.
- - In Jenkins project we usually use [OpenJDK](http://openjdk.java.net/),
+ - In Jenkins project we usually use [OpenJDK],
but you can use other JDKs as well.
- - Java 9 is **not supported** in Jenkins.
- * Maven 3.5.3 or above. You can download it [here](https://maven.apache.org/download.cgi)
- * Any IDE which supports importing Maven projects
-4. Setup your development environment as described in [Preparing for Plugin Development](https://jenkins.io/doc/developer/tutorial/prepare/)
+ - Java 9+ is **not supported** in Jenkins.
+ * Maven 3.5.3 or above. You can [download maven].
+ * Any IDE which supports importing Maven projects.
+4. Setup your development environment as described in [Preparing for Plugin Development]
If you want to contribute to Jenkins or just learn about the project,
you can start by fixing some easier issues.
In the Jenkins issue tracker we mark such issues as `newbie-friendly`.
You can find them
-using [this query](https://issues.jenkins-ci.org/issues/?jql=project%20%3D%20JENKINS%20AND%20status%20in%20(Open%2C%20%22In%20Progress%22%2C%20Reopened)%20AND%20component%20%3D%20core%20AND%20labels%20in%20(newbie-friendly)).
+using this query for [newbie friendly issues].
## Building and Debugging
The build flow for Jenkins core is built around Maven.
-Building and debugging process is described [here](https://jenkins.io/doc/developer/building/).
+There is a description of the [building and debugging process].
If you want simply to have the `jenkins.war` file as fast as possible without tests, run:
mvn clean package -pl war -am -DskipTests -Dfindbugs.skip
The WAR file will be created in `war/target/jenkins.war`.
-After that you can start Jenkins using Java CLI ([guide](https://wiki.jenkins.io/display/JENKINS/Starting+and+Accessing+Jenkins)).
+After that you can start Jenkins using Java CLI ([guide]).
If you want to debug this WAR file without using Maven plugins,
-You can just start the executable with [Remote Debug Flags](https://stackoverflow.com/questions/975271/remote-debugging-a-java-application)
+You can just start the executable with [Remote Debug Flags]
and then attach IDE Debugger to it.
## Testing changes
@@ -53,12 +53,12 @@ There are 3 profiles for tests:
* `all-tests` - Runs all tests, with re-run (default)
In addition to the included tests, you can also find extra integration and UI
-tests in the [Acceptance Test Harness (ATH)](https://github.com/jenkinsci/acceptance-test-harness) repository.
+tests in the [Acceptance Test Harness (ATH)] repository.
If you propose complex UI changes, you should create new ATH tests for them.
## Proposing Changes
-The Jenkins project source code repositories are hosted GitHub.
+The Jenkins project source code repositories are hosted at GitHub.
All proposed changes are submitted and code reviewed using the _GitHub Pull Request_ process.
To submit a pull request:
@@ -69,8 +69,8 @@ It is a good practice is to create branches instead of pushing to master.
3. Select `jenkinsci` as _base fork_ and `master` as `base`, then click _Create Pull Request_
* We integrate all changes into the master branch towards the Weekly releases
* After that the changes may be backported to the current LTS baseline by the LTS Team.
- The backporting process is described [here](https://jenkins.io/download/lts/).
-4. Fill in the Pull Request description according to the [proposed template](.github/PULL_REQUEST_TEMPLATE.md).
+ Read more about the [backporting process]
+4. Fill in the Pull Request description according to the [proposed template].
5. Click _Create Pull Request_
6. Wait for CI results/reviews, process the feedback.
* If you do not get feedback after 3 days, feel free to ping `@jenkinsci/code-reviewers` to CC.
@@ -83,25 +83,25 @@ There is no additional action required from pull request authors at this point.
## Copyright
-Jenkins core is licensed under [MIT license](./LICENSE.txt), with a few exceptions in bundled classes.
+Jenkins core is licensed under [MIT license], with a few exceptions in bundled classes.
We consider all contributions as MIT unless it's explicitly stated otherwise.
MIT-incompatible code contributions will be rejected.
Contributions under MIT-compatible licenses may be also rejected if they are not ultimately necessary.
-We **Do NOT** require pull request submitters to sign the [contributor agreement](https://wiki.jenkins.io/display/JENKINS/Copyright+on+source+code)
+We **Do NOT** require pull request submitters to sign the [contributor agreement]
as long as the code is licensed under MIT and merged by one of the contributors with the signed agreement.
We still encourage people to sign the contributor agreement if they intend to submit more than a few pull requests.
Signing is also a mandatory prerequisite for getting merge/push permissions to core repositories
-and for joining teams like [Jenkins Security Team](https://jenkins.io/security/#team).
+and for joining teams like [Jenkins Security Team].
## Continuous Integration
The Jenkins project has a Continuous Integration server... powered by Jenkins, of course.
-It is located at [ci.jenkins.io](https://ci.jenkins.io/).
+It is located at [ci.jenkins.io].
-The Jenkins project uses [Jenkins Pipeline](https://jenkins.io/doc/book/pipeline/) to run builds.
-The code for the core build flow is stored in the [Jenkinsfile](./Jenkinsfile) in the repository root.
+The Jenkins project uses [Jenkins Pipeline] to run builds.
+The code for the core build flow is stored in the [Jenkinsfile] in the repository root.
If you want to update that build flow (e.g. "add more checks"),
just submit a pull request.
@@ -112,4 +112,20 @@ just submit a pull request.
* [Beginners Guide To Contributing](https://wiki.jenkins.io/display/JENKINS/Beginners+Guide+to+Contributing)
* [List of newbie-friendly issues in the core](https://issues.jenkins-ci.org/issues/?jql=project%20%3D%20JENKINS%20AND%20status%20in%20(Open%2C%20%22In%20Progress%22%2C%20Reopened)%20AND%20component%20%3D%20core%20AND%20labels%20in%20(newbie-friendly))
-
+[download maven]: https://maven.apache.org/download.cgi
+[Preparing for Plugin Development]: https://jenkins.io/doc/developer/tutorial/prepare/
+[newbie friendly issues]: https://issues.jenkins-ci.org/issues/?jql=project%20%3D%20JENKINS%20AND%20status%20in%20(Open%2C%20%22In%20Progress%22%2C%20Reopened)%20AND%20component%20%3D%20core%20AND%20labels%20in%20(newbie-friendly)
+[OpenJDK]: http://openjdk.java.net/
+[Participate]: https://jenkins.io/participate/
+[building and debugging process]: https://jenkins.io/doc/developer/building/
+[guide]: https://wiki.jenkins.io/display/JENKINS/Starting+and+Accessing+Jenkins
+[Remote Debug Flags]: https://stackoverflow.com/questions/975271/remote-debugging-a-java-application
+[Acceptance Test Harness (ATH)]: https://github.com/jenkinsci/acceptance-test-harness
+[backporting process]: https://jenkins.io/download/lts/
+[proposed template]: .github/PULL_REQUEST_TEMPLATE.md
+[MIT license]: ./LICENSE.txt
+[contributor agreement]: https://wiki.jenkins.io/display/JENKINS/Copyright+on+source+code
+[Jenkins Security Team]: https://jenkins.io/security/#team
+[ci.jenkins.io]: https://ci.jenkins.io/
+[Jenkins Pipeline]: https://jenkins.io/doc/book/pipeline/
+[Jenkinsfile]: ./Jenkinsfile
\ No newline at end of file
diff --git a/Jenkinsfile b/Jenkinsfile
index f6eb3e2c0cfdc06c586ea5c1a1a46689b9811957..f6b139aaec9d2ce37672dc1a977fda58decf8ab5 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -13,11 +13,11 @@
def runTests = true
def failFast = false
-properties([buildDiscarder(logRotator(numToKeepStr: '50', artifactNumToKeepStr: '20')), durabilityHint('PERFORMANCE_OPTIMIZED')])
+properties([buildDiscarder(logRotator(numToKeepStr: '50', artifactNumToKeepStr: '3')), durabilityHint('PERFORMANCE_OPTIMIZED')])
// see https://github.com/jenkins-infra/documentation/blob/master/ci.adoc for information on what node types are available
def buildTypes = ['Linux', 'Windows']
-def jdks = [8]
+def jdks = [8, 11]
def builds = [:]
for(i = 0; i < buildTypes.size(); i++) {
@@ -49,7 +49,7 @@ for(j = 0; j < jdks.size(); j++) {
if(isUnix()) {
sh mvnCmd
- sh 'test `git status --short | tee /dev/stderr | wc --bytes` -eq 0'
+ sh 'git add . && git diff --exit-code HEAD'
} else {
bat mvnCmd
}
@@ -61,12 +61,13 @@ for(j = 0; j < jdks.size(); j++) {
stage("${buildType} Publishing") {
if (runTests) {
junit healthScaleFactor: 20.0, testResults: '*/target/surefire-reports/*.xml'
+ archiveArtifacts allowEmptyArchive: true, artifacts: '**/target/surefire-reports/*.dumpstream'
}
- if (buildType == 'Linux') {
+ if (buildType == 'Linux' && jdk == jdks[0]) {
def changelist = readFile(changelistF)
dir(m2repo) {
archiveArtifacts artifacts: "**/*$changelist/*$changelist*",
- excludes: '**/*.lastUpdated,**/jenkins-test/',
+ excludes: '**/*.lastUpdated,**/jenkins-test*/',
allowEmptyArchive: true, // in case we forgot to reincrementalify
fingerprint: true
}
diff --git a/README.md b/README.md
index 6dc6c47ecef715c771fcfdeb9ccd83cc45cf4962..7ef6c08a8ec40120d755931ce679e5ca870c5bda 100644
--- a/README.md
+++ b/README.md
@@ -23,7 +23,7 @@ Non-source downloads such as WAR files and several Linux packages can be found o
Our latest and greatest source of Jenkins can be found on [GitHub]. Fork us!
# Contributing to Jenkins
-Follow [contributing](CONTRIBUTING.md) file.
+Follow the [contributing](CONTRIBUTING.md) file.
# News and Website
All information about Jenkins can be found on our [website]. Follow us on Twitter [@jenkinsci].
diff --git a/cli/pom.xml b/cli/pom.xml
index 670e416a26dd5c5f5bc6943ad046e9f8c4c41c00..b02df3123ec17c21c9e7c5d8e12d44a9e6eec3ad 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -58,7 +58,7 @@
org.jvnet.localizerlocalizer
- 1.24
+ 1.26org.apache.sshd
@@ -66,6 +66,12 @@
1.7.0true
+
+
+ net.i2p.crypto
+ eddsa
+ 0.3.0
+ org.slf4jslf4j-jdk14
diff --git a/cli/src/main/java/hudson/cli/CLI.java b/cli/src/main/java/hudson/cli/CLI.java
index 0ffef53f9305a6e9d260826e092e805fe9b8465e..5b436b02b085d9ca964561828d4b2fdeafb41715 100644
--- a/cli/src/main/java/hudson/cli/CLI.java
+++ b/cli/src/main/java/hudson/cli/CLI.java
@@ -24,54 +24,25 @@
package hudson.cli;
import hudson.cli.client.Messages;
-import hudson.remoting.Channel;
-import hudson.remoting.NamingThreadFactory;
-import hudson.remoting.PingThread;
-import hudson.remoting.Pipe;
-import hudson.remoting.RemoteInputStream;
-import hudson.remoting.RemoteOutputStream;
-import hudson.remoting.SocketChannelStream;
-import hudson.remoting.SocketOutputStream;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.BufferedReader;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.Closeable;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.io.PrintStream;
-import java.io.StringReader;
-import java.net.HttpURLConnection;
-import java.net.InetSocketAddress;
-import java.net.Socket;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
-import java.security.PublicKey;
import java.security.SecureRandom;
-import java.security.Signature;
-import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -81,240 +52,10 @@ import org.apache.commons.lang.StringUtils;
/**
* CLI entry point to Jenkins.
- *
- * @author Kohsuke Kawaguchi
*/
-public class CLI implements AutoCloseable {
- private final ExecutorService pool;
- private final Channel channel;
- private final CliEntryPoint entryPoint;
- private final boolean ownsPool;
- private final List closables = new ArrayList(); // stuff to close in the close method
- private final String httpsProxyTunnel;
- private final String authorization;
+public class CLI {
- /**
- * For tests only.
- * @deprecated Specific to {@link Mode#REMOTING}.
- */
- @Deprecated
- public CLI(URL jenkins) throws IOException, InterruptedException {
- this(jenkins,null);
- }
-
- /**
- * @deprecated
- * Use {@link CLIConnectionFactory} to create {@link CLI}
- */
- @Deprecated
- public CLI(URL jenkins, ExecutorService exec) throws IOException, InterruptedException {
- this(jenkins,exec,null);
- }
-
- /**
- * @deprecated
- * Use {@link CLIConnectionFactory} to create {@link CLI}
- */
- @Deprecated
- public CLI(URL jenkins, ExecutorService exec, String httpsProxyTunnel) throws IOException, InterruptedException {
- this(new CLIConnectionFactory().url(jenkins).executorService(exec).httpsProxyTunnel(httpsProxyTunnel));
- }
-
- /**
- * @deprecated Specific to {@link Mode#REMOTING}.
- */
- @Deprecated
- /*package*/ CLI(CLIConnectionFactory factory) throws IOException, InterruptedException {
- URL jenkins = factory.jenkins;
- this.httpsProxyTunnel = factory.httpsProxyTunnel;
- this.authorization = factory.authorization;
- ExecutorService exec = factory.exec;
-
- ownsPool = exec==null;
- pool = exec!=null ? exec : Executors.newCachedThreadPool(new NamingThreadFactory(Executors.defaultThreadFactory(), "CLI.pool"));
-
- Channel _channel;
- try {
- _channel = connectViaCliPort(jenkins, getCliTcpPort(jenkins));
- } catch (IOException e) {
- LOGGER.log(Level.FINE, "Failed to connect via CLI port. Falling back to HTTP", e);
- try {
- _channel = connectViaHttp(jenkins);
- } catch (IOException e2) {
- e.addSuppressed(e2);
- throw e;
- }
- }
- this.channel = _channel;
-
- // execute the command
- entryPoint = (CliEntryPoint)_channel.waitForRemoteProperty(CliEntryPoint.class.getName());
-
- if(entryPoint.protocolVersion()!=CliEntryPoint.VERSION)
- throw new IOException(Messages.CLI_VersionMismatch());
- }
-
- /**
- * @deprecated Specific to {@link Mode#REMOTING}.
- */
- @Deprecated
- private Channel connectViaHttp(URL url) throws IOException {
- LOGGER.log(FINE, "Trying to connect to {0} via Remoting over HTTP", url);
-
- FullDuplexHttpStream con = new FullDuplexHttpStream(url, "cli?remoting=true", authorization);
- Channel ch = new Channel("Chunked connection to " + url,
- pool,con.getInputStream(),con.getOutputStream());
- final long interval = 15*1000;
- final long timeout = (interval * 3) / 4;
- new PingThread(ch,timeout,interval) {
- protected void onDead() {
- // noop. the point of ping is to keep the connection alive
- // as most HTTP servers have a rather short read time out
- }
- }.start();
- return ch;
- }
-
- /**
- * @deprecated Specific to {@link Mode#REMOTING}.
- */
- @Deprecated
- private Channel connectViaCliPort(URL jenkins, CliPort clip) throws IOException {
- LOGGER.log(FINE, "Trying to connect directly via Remoting over TCP/IP to {0}", clip.endpoint);
-
- if (authorization != null) {
- LOGGER.warning("-auth ignored when using JNLP agent port");
- }
-
- final Socket s = new Socket();
- // this prevents a connection from silently terminated by the router in between or the other peer
- // and that goes without unnoticed. However, the time out is often very long (for example 2 hours
- // by default in Linux) that this alone is enough to prevent that.
- s.setKeepAlive(true);
- // we take care of buffering on our own
- s.setTcpNoDelay(true);
- OutputStream out;
-
- if (httpsProxyTunnel!=null) {
- String[] tokens = httpsProxyTunnel.split(":");
- LOGGER.log(Level.FINE, "Using HTTP proxy {0}:{1} to connect to CLI port", new Object[]{tokens[0], tokens[1]});
- s.connect(new InetSocketAddress(tokens[0], Integer.parseInt(tokens[1])));
- PrintStream o = new PrintStream(s.getOutputStream());
- o.print("CONNECT " + clip.endpoint.getHostString() + ":" + clip.endpoint.getPort() + " HTTP/1.0\r\n\r\n");
-
- // read the response from the proxy
- ByteArrayOutputStream rsp = new ByteArrayOutputStream();
- while (!rsp.toString("ISO-8859-1").endsWith("\r\n\r\n")) {
- int ch = s.getInputStream().read();
- if (ch<0) throw new IOException("Failed to read the HTTP proxy response: "+rsp);
- rsp.write(ch);
- }
- String head = new BufferedReader(new StringReader(rsp.toString("ISO-8859-1"))).readLine();
-
- if (head == null) {
- throw new IOException("Unexpected empty response");
- }
- if (!(head.startsWith("HTTP/1.0 200 ") || head.startsWith("HTTP/1.1 200 "))) {
- s.close();
- LOGGER.log(Level.SEVERE, "Failed to tunnel the CLI port through the HTTP proxy. Falling back to HTTP.");
- throw new IOException("Failed to establish a connection through HTTP proxy: " + rsp);
- }
-
- // HTTP proxies (at least the one I tried --- squid) doesn't seem to do half-close very well.
- // So instead of relying on it, we'll just send the close command and then let the server
- // cut their side, then close the socket after the join.
- out = new SocketOutputStream(s) {
- @Override
- public void close() throws IOException {
- // ignore
- }
- };
- } else {
- s.connect(clip.endpoint,3000);
- out = SocketChannelStream.out(s);
- }
-
- closables.add(new Closeable() {
- public void close() throws IOException {
- s.close();
- }
- });
-
- Connection c = new Connection(SocketChannelStream.in(s),out);
-
- switch (clip.version) {
- case 1:
- DataOutputStream dos = new DataOutputStream(s.getOutputStream());
- dos.writeUTF("Protocol:CLI-connect");
- // we aren't checking greeting from the server here because I'm too lazy. It gets ignored by Channel constructor.
- break;
- case 2:
- DataInputStream dis = new DataInputStream(s.getInputStream());
- dos = new DataOutputStream(s.getOutputStream());
- dos.writeUTF("Protocol:CLI2-connect");
- String greeting = dis.readUTF();
- if (!greeting.equals("Welcome"))
- throw new IOException("Handshaking failed: "+greeting);
- try {
- byte[] secret = c.diffieHellman(false).generateSecret();
- SecretKey sessionKey = new SecretKeySpec(Connection.fold(secret,128/8),"AES");
- c = c.encryptConnection(sessionKey,"AES/CFB8/NoPadding");
-
- // validate the instance identity, so that we can be sure that we are talking to the same server
- // and there's no one in the middle.
- byte[] signature = c.readByteArray();
-
- if (clip.identity!=null) {
- Signature verifier = Signature.getInstance("SHA1withRSA");
- verifier.initVerify(clip.getIdentity());
- verifier.update(secret);
- if (!verifier.verify(signature))
- throw new IOException("Server identity signature validation failed.");
- }
-
- } catch (GeneralSecurityException e) {
- throw (IOException)new IOException("Failed to negotiate transport security").initCause(e);
- }
- }
-
- return new Channel("CLI connection to "+jenkins, pool,
- new BufferedInputStream(c.in), new BufferedOutputStream(c.out));
- }
-
- /**
- * If the server advertises CLI endpoint, returns its location.
- * @deprecated Specific to {@link Mode#REMOTING}.
- */
- @Deprecated
- protected CliPort getCliTcpPort(URL url) throws IOException {
- if (url.getHost()==null || url.getHost().length()==0) {
- throw new IOException("Invalid URL: "+url);
- }
- URLConnection head = url.openConnection();
- try {
- head.connect();
- } catch (IOException e) {
- throw (IOException)new IOException("Failed to connect to "+url).initCause(e);
- }
-
- String h = head.getHeaderField("X-Jenkins-CLI-Host");
- if (h==null) h = head.getURL().getHost();
- String p1 = head.getHeaderField("X-Jenkins-CLI-Port");
- if (p1==null) p1 = head.getHeaderField("X-Hudson-CLI-Port"); // backward compatibility
- String p2 = head.getHeaderField("X-Jenkins-CLI2-Port");
-
- String identity = head.getHeaderField("X-Instance-Identity");
-
- flushURLConnection(head);
- if (p1==null && p2==null) {
- verifyJenkinsConnection(head);
-
- throw new IOException("No X-Jenkins-CLI2-Port among " + head.getHeaderFields().keySet());
- }
-
- if (p2!=null) return new CliPort(new InetSocketAddress(h,Integer.parseInt(p2)),identity,2);
- else return new CliPort(new InetSocketAddress(h,Integer.parseInt(p1)),identity,1);
- }
+ private CLI() {}
/**
* Make sure the connection is open against Jenkins server.
@@ -337,91 +78,6 @@ public class CLI implements AutoCloseable {
}
}
- /**
- * Flush the supplied {@link URLConnection} input and close the
- * connection nicely.
- * @param conn the connection to flush/close
- */
- private void flushURLConnection(URLConnection conn) {
- byte[] buf = new byte[1024];
- try {
- InputStream is = conn.getInputStream();
- while (is.read(buf) >= 0) {
- // Ignore
- }
- is.close();
- } catch (IOException e) {
- try {
- InputStream es = ((HttpURLConnection)conn).getErrorStream();
- if (es!=null) {
- while (es.read(buf) >= 0) {
- // Ignore
- }
- es.close();
- }
- } catch (IOException ex) {
- // Ignore
- }
- }
- }
-
- /**
- * Shuts down the channel and closes the underlying connection.
- */
- public void close() throws IOException, InterruptedException {
- channel.close();
- channel.join();
- if(ownsPool)
- pool.shutdown();
- for (Closeable c : closables)
- c.close();
- }
-
- public int execute(List args, InputStream stdin, OutputStream stdout, OutputStream stderr) {
- return entryPoint.main(args, Locale.getDefault(),
- new RemoteInputStream(stdin),
- new RemoteOutputStream(stdout),
- new RemoteOutputStream(stderr));
- }
-
- public int execute(List args) {
- return execute(args, System.in, System.out, System.err);
- }
-
- public int execute(String... args) {
- return execute(Arrays.asList(args));
- }
-
- /**
- * Returns true if the named command exists.
- */
- public boolean hasCommand(String name) {
- return entryPoint.hasCommand(name);
- }
-
- /**
- * Accesses the underlying communication channel.
- * @since 1.419
- */
- public Channel getChannel() {
- return channel;
- }
-
- /**
- * Attempts to lift the security restriction on the underlying channel.
- * This requires the administer privilege on the server.
- *
- * @throws SecurityException
- * If we fail to upgrade the connection.
- */
- public void upgrade() {
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- if (execute(Arrays.asList("groovy", "="),
- new ByteArrayInputStream("hudson.remoting.Channel.current().setRestricted(false)".getBytes()),
- out,out)!=0)
- throw new SecurityException(out.toString()); // failed to upgrade
- }
-
public static void main(final String[] _args) throws Exception {
try {
System.exit(_main(_args));
@@ -435,12 +91,10 @@ public class CLI implements AutoCloseable {
}
}
- private enum Mode {HTTP, SSH, REMOTING}
+ private enum Mode {HTTP, SSH}
public static int _main(String[] _args) throws Exception {
List args = Arrays.asList(_args);
PrivateKeyProvider provider = new PrivateKeyProvider();
- boolean sshAuthRequestedExplicitly = false;
- String httpProxy=null;
String url = System.getenv("JENKINS_URL");
@@ -484,13 +138,8 @@ public class CLI implements AutoCloseable {
continue;
}
if (head.equals("-remoting")) {
- if (mode != null) {
- printUsage("-remoting clashes with previously defined mode " + mode);
- return -1;
- }
- mode = Mode.REMOTING;
- args = args.subList(1, args.size());
- continue;
+ printUsage("-remoting mode is no longer supported");
+ return -1;
}
if(head.equals("-s") && args.size()>=2) {
url = args.get(1);
@@ -526,7 +175,6 @@ public class CLI implements AutoCloseable {
provider.readFrom(f);
args = args.subList(2,args.size());
- sshAuthRequestedExplicitly = true;
continue;
}
if (head.equals("-strictHostKey")) {
@@ -544,11 +192,6 @@ public class CLI implements AutoCloseable {
args = args.subList(2, args.size());
continue;
}
- if(head.equals("-p") && args.size()>=2) {
- httpProxy = args.get(1);
- args = args.subList(2,args.size());
- continue;
- }
if (head.equals("-logger") && args.size() >= 2) {
Level level = parse(args.get(1));
for (Handler h : Logger.getLogger("").getHandlers()) {
@@ -616,7 +259,7 @@ public class CLI implements AutoCloseable {
LOGGER.warning("Warning: -user ignored unless using -ssh");
}
- CLIConnectionFactory factory = new CLIConnectionFactory().url(url).httpsProxyTunnel(httpProxy);
+ CLIConnectionFactory factory = new CLIConnectionFactory();
String userInfo = new URL(url).getUserInfo();
if (userInfo != null) {
factory = factory.basicAuth(userInfo);
@@ -628,39 +271,7 @@ public class CLI implements AutoCloseable {
return plainHttpConnection(url, args, factory);
}
- CLI cli = factory.connect();
- try {
- if (provider.hasKeys()) {
- try {
- // TODO: server verification
- cli.authenticate(provider.getKeys());
- } catch (IllegalStateException e) {
- if (sshAuthRequestedExplicitly) {
- LOGGER.warning("The server doesn't support public key authentication");
- return -1;
- }
- } catch (UnsupportedOperationException e) {
- if (sshAuthRequestedExplicitly) {
- LOGGER.warning("The server doesn't support public key authentication");
- return -1;
- }
- } catch (GeneralSecurityException e) {
- if (sshAuthRequestedExplicitly) {
- LOGGER.log(WARNING, null, e);
- return -1;
- }
- LOGGER.warning("Failed to authenticate with your SSH keys. Proceeding as anonymous");
- LOGGER.log(FINE, null, e);
- }
- }
-
- // execute the command
- // Arrays.asList is not serializable --- see 6835580
- args = new ArrayList(args);
- return cli.execute(args, System.in, System.out, System.err);
- } finally {
- cli.close();
- }
+ throw new AssertionError();
}
private static int plainHttpConnection(String url, List args, CLIConnectionFactory factory) throws IOException, InterruptedException {
@@ -784,47 +395,6 @@ public class CLI implements AutoCloseable {
return loadKey(pemString, null);
}
- /**
- * Authenticate ourselves against the server.
- *
- * @return
- * identity of the server represented as a public key.
- * @deprecated Specific to {@link Mode#REMOTING}.
- */
- @Deprecated
- public PublicKey authenticate(Iterable privateKeys) throws IOException, GeneralSecurityException {
- Pipe c2s = Pipe.createLocalToRemote();
- Pipe s2c = Pipe.createRemoteToLocal();
- entryPoint.authenticate("ssh",c2s, s2c);
- Connection c = new Connection(s2c.getIn(), c2s.getOut());
-
- try {
- byte[] sharedSecret = c.diffieHellman(false).generateSecret();
- PublicKey serverIdentity = c.verifyIdentity(sharedSecret);
-
- // try all the public keys
- for (KeyPair key : privateKeys) {
- c.proveIdentity(sharedSecret,key);
- if (c.readBoolean())
- return serverIdentity; // succeeded
- }
- if (privateKeys.iterator().hasNext())
- throw new GeneralSecurityException("Authentication failed. No private key accepted.");
- else
- throw new GeneralSecurityException("No private key is available for use in authentication");
- } finally {
- c.close();
- }
- }
-
- /**
- * @deprecated Specific to {@link Mode#REMOTING}.
- */
- @Deprecated
- public PublicKey authenticate(KeyPair key) throws IOException, GeneralSecurityException {
- return authenticate(Collections.singleton(key));
- }
-
/** For access from {@code HelpCommand}. */
static String usage() {
return Messages.CLI_Usage();
diff --git a/cli/src/main/java/hudson/cli/CLIConnectionFactory.java b/cli/src/main/java/hudson/cli/CLIConnectionFactory.java
index 894e4c0fdac60ff446e3a67ecf68e44192d93f97..288696b0da0828be52b2bd7c8cafd62e90719914 100644
--- a/cli/src/main/java/hudson/cli/CLIConnectionFactory.java
+++ b/cli/src/main/java/hudson/cli/CLIConnectionFactory.java
@@ -2,10 +2,6 @@ package hudson.cli;
import org.apache.commons.codec.binary.Base64;
-import java.io.IOException;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.concurrent.ExecutorService;
/**
* Fluent-API to instantiate {@link CLI}.
@@ -13,41 +9,8 @@ import java.util.concurrent.ExecutorService;
* @author Kohsuke Kawaguchi
*/
public class CLIConnectionFactory {
- URL jenkins;
- ExecutorService exec;
- String httpsProxyTunnel;
String authorization;
- /**
- * Top URL of the Jenkins to connect to.
- */
- public CLIConnectionFactory url(URL jenkins) {
- this.jenkins = jenkins;
- return this;
- }
-
- public CLIConnectionFactory url(String jenkins) throws MalformedURLException {
- return url(new URL(jenkins));
- }
-
- /**
- * This {@link ExecutorService} is used to execute closures received from the server.
- * Used only in Remoting mode.
- */
- public CLIConnectionFactory executorService(ExecutorService es) {
- this.exec = es;
- return this;
- }
-
- /**
- * Configures the HTTP proxy that we use for making a plain TCP/IP connection.
- * "host:port" that points to an HTTP proxy or null.
- */
- public CLIConnectionFactory httpsProxyTunnel(String value) {
- this.httpsProxyTunnel = value;
- return this;
- }
-
/**
* For CLI connection that goes through HTTP, sometimes you need
* to pass in the custom authentication header (before Jenkins even get to authenticate
@@ -74,11 +37,4 @@ public class CLIConnectionFactory {
return authorization("Basic " + new String(Base64.encodeBase64((userInfo).getBytes())));
}
- /**
- * @deprecated Specific to Remoting-based protocol.
- */
- @Deprecated
- public CLI connect() throws IOException, InterruptedException {
- return new CLI(this);
- }
}
diff --git a/cli/src/main/java/hudson/cli/CliEntryPoint.java b/cli/src/main/java/hudson/cli/CliEntryPoint.java
deleted file mode 100644
index fe1f6c835097888be71632babbe31f1b6530fab5..0000000000000000000000000000000000000000
--- a/cli/src/main/java/hudson/cli/CliEntryPoint.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * The MIT License
- *
- * Copyright (c) 2004-2009, Sun Microsystems, Inc.
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-package hudson.cli;
-
-import hudson.remoting.Pipe;
-
-import java.io.OutputStream;
-import java.io.InputStream;
-import java.util.List;
-import java.util.Locale;
-
-/**
- * Remotable interface for CLI entry point on the server side.
- *
- * @author Kohsuke Kawaguchi
- * @deprecated Specific to Remoting-based protocol.
- */
-@Deprecated
-public interface CliEntryPoint {
- /**
- * Just like the static main method.
- *
- * @param locale
- * Locale of this client.
- */
- int main(List args, Locale locale, InputStream stdin, OutputStream stdout, OutputStream stderr);
-
- /**
- * Does the named command exist?
- */
- boolean hasCommand(String name);
-
- /**
- * Returns {@link #VERSION}, so that the client and the server can detect version incompatibility
- * gracefully.
- */
- int protocolVersion();
-
- /**
- * Initiates authentication out of band.
- *
- * This method starts two-way byte channel that allows the client and the server to perform authentication.
- * The current supported implementation is based on SSH public key authentication that mutually authenticates
- * clients and servers.
- *
- * @param protocol
- * Currently only "ssh" is supported.
- * @throws UnsupportedOperationException
- * If the specified protocol is not supported by the server.
- */
- void authenticate(String protocol, Pipe c2s, Pipe s2c);
-
- int VERSION = 1;
-}
diff --git a/cli/src/main/java/hudson/cli/CliPort.java b/cli/src/main/java/hudson/cli/CliPort.java
deleted file mode 100644
index 56165e766ba6186489d4e3017b0a7a35510358ab..0000000000000000000000000000000000000000
--- a/cli/src/main/java/hudson/cli/CliPort.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package hudson.cli;
-
-import org.apache.commons.codec.binary.Base64;
-
-import java.net.InetSocketAddress;
-import java.security.GeneralSecurityException;
-import java.security.KeyFactory;
-import java.security.PublicKey;
-import java.security.spec.X509EncodedKeySpec;
-
- /**
- * @deprecated Specific to Remoting mode.
- */
-public final class CliPort {
- /**
- * The TCP endpoint to talk to.
- */
- final InetSocketAddress endpoint;
-
- /**
- * CLI protocol version. 1 and 2 are currently defined.
- */
- final int version;
-
- /**
- * Server instance identity. Can be null.
- */
- final String identity;
-
- public CliPort(InetSocketAddress endpoint, String identity, int version) {
- this.endpoint = endpoint;
- this.identity = identity;
- this.version = version;
- }
-
- /**
- * Gets the public part of the RSA key that represents the server identity.
- */
- public PublicKey getIdentity() throws GeneralSecurityException {
- if (identity==null) return null;
- byte[] image = Base64.decodeBase64(identity);
- return KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(image));
- }
-}
diff --git a/cli/src/main/java/hudson/cli/Connection.java b/cli/src/main/java/hudson/cli/Connection.java
index eacc567a7e5d822afc9e4d1b20afc5473cbb0b3b..1961447778d04ab829c9b550305ac016309bcd35 100644
--- a/cli/src/main/java/hudson/cli/Connection.java
+++ b/cli/src/main/java/hudson/cli/Connection.java
@@ -58,8 +58,9 @@ import java.security.spec.X509EncodedKeySpec;
import org.jenkinsci.remoting.util.AnonymousClassWarnings;
/**
- * Used by Jenkins core only in deprecated Remoting-based CLI.
+ * @deprecated No longer used.
*/
+@Deprecated
public class Connection {
public final InputStream in;
public final OutputStream out;
diff --git a/cli/src/main/java/hudson/cli/FullDuplexHttpStream.java b/cli/src/main/java/hudson/cli/FullDuplexHttpStream.java
index 9b18142b7824257af32e510e4eb99d5cc677cf69..723e97f16c26805a3550a0d9b8fcb17476396fee 100644
--- a/cli/src/main/java/hudson/cli/FullDuplexHttpStream.java
+++ b/cli/src/main/java/hudson/cli/FullDuplexHttpStream.java
@@ -9,8 +9,6 @@ import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
-import org.apache.commons.codec.binary.Base64;
-
/**
* Creates a capacity-unlimited bi-directional {@link InputStream}/{@link OutputStream} pair over
* HTTP, which is a request/response protocol.
@@ -19,10 +17,6 @@ import org.apache.commons.codec.binary.Base64;
*/
public class FullDuplexHttpStream {
private final URL base;
- /**
- * Authorization header value needed to get through the HTTP layer.
- */
- private final String authorization;
private final OutputStream output;
private final InputStream input;
@@ -43,28 +37,6 @@ public class FullDuplexHttpStream {
return output;
}
- @Deprecated
- public FullDuplexHttpStream(URL target) throws IOException {
- this(target,basicAuth(target.getUserInfo()));
- }
-
- private static String basicAuth(String userInfo) {
- if (userInfo != null)
- return "Basic "+new String(Base64.encodeBase64(userInfo.getBytes()));
- return null;
- }
-
- /**
- * @param target something like {@code http://jenkins/cli?remoting=true}
- * which we then need to split into {@code http://jenkins/} + {@code cli?remoting=true}
- * in order to construct a crumb issuer request
- * @deprecated use {@link #FullDuplexHttpStream(URL, String, String)} instead
- */
- @Deprecated
- public FullDuplexHttpStream(URL target, String authorization) throws IOException {
- this(new URL(target.toString().replaceFirst("/cli.*$", "/")), target.toString().replaceFirst("^.+/(cli.*)$", "$1"), authorization);
- }
-
/**
* @param base the base URL of Jenkins
* @param relativeTarget
@@ -81,7 +53,6 @@ public class FullDuplexHttpStream {
}
this.base = tryToResolveRedirects(base, authorization);
- this.authorization = authorization;
URL target = new URL(this.base, relativeTarget);
diff --git a/cli/src/main/java/hudson/cli/PrivateKeyProvider.java b/cli/src/main/java/hudson/cli/PrivateKeyProvider.java
index 9f87dd4a7b38a174229ccfc02b04779b51002951..bbf7873e2917f1b4edd456c8d53a23d48bf74459 100644
--- a/cli/src/main/java/hudson/cli/PrivateKeyProvider.java
+++ b/cli/src/main/java/hudson/cli/PrivateKeyProvider.java
@@ -24,26 +24,25 @@
package hudson.cli;
import static java.util.logging.Level.FINE;
+import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.Console;
import java.io.DataInputStream;
+import java.io.ByteArrayInputStream;
import java.io.File;
-import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.security.GeneralSecurityException;
-import java.security.KeyFactory;
import java.security.KeyPair;
-import java.security.spec.DSAPrivateKeySpec;
-import java.security.spec.DSAPublicKeySpec;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;
-import com.trilead.ssh2.crypto.PEMDecoder;
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.util.security.SecurityUtils;
/**
* Read DSA or RSA key from file(s) asking for password interactively.
@@ -141,22 +140,9 @@ public class PrivateKeyProvider {
}
public static KeyPair loadKey(String pemString, String passwd) throws IOException, GeneralSecurityException {
- Object key = PEMDecoder.decode(pemString.toCharArray(), passwd);
- if (key instanceof com.trilead.ssh2.signature.RSAPrivateKey) {
- com.trilead.ssh2.signature.RSAPrivateKey x = (com.trilead.ssh2.signature.RSAPrivateKey)key;
-
- return x.toJCEKeyPair();
- }
- if (key instanceof com.trilead.ssh2.signature.DSAPrivateKey) {
- com.trilead.ssh2.signature.DSAPrivateKey x = (com.trilead.ssh2.signature.DSAPrivateKey)key;
- KeyFactory kf = KeyFactory.getInstance("DSA");
-
- return new KeyPair(
- kf.generatePublic(new DSAPublicKeySpec(x.getY(), x.getP(), x.getQ(), x.getG())),
- kf.generatePrivate(new DSAPrivateKeySpec(x.getX(), x.getP(), x.getQ(), x.getG())));
- }
-
- throw new UnsupportedOperationException("Unrecognizable key format: " + key);
+ return SecurityUtils.loadKeyPairIdentity("key",
+ new ByteArrayInputStream(pemString.getBytes(UTF_8)),
+ FilePasswordProvider.of(passwd));
}
private static final Logger LOGGER = Logger.getLogger(PrivateKeyProvider.class.getName());
diff --git a/cli/src/main/resources/hudson/cli/client/Messages.properties b/cli/src/main/resources/hudson/cli/client/Messages.properties
index 2e7fde44cb075a87813b4cced7eb48f2952c85cd..64a91d5ca56382027ae6b0259e00bf769d4a6223 100644
--- a/cli/src/main/resources/hudson/cli/client/Messages.properties
+++ b/cli/src/main/resources/hudson/cli/client/Messages.properties
@@ -2,24 +2,21 @@ CLI.Usage=Jenkins CLI\n\
Usage: java -jar jenkins-cli.jar [-s URL] command [opts...] args...\n\
Options:\n\
\ -s URL : the server URL (defaults to the JENKINS_URL env var)\n\
- \ -http : use a plain CLI protocol over HTTP(S) (the default; mutually exclusive with -ssh and -remoting)\n\
+ \ -http : use a plain CLI protocol over HTTP(S) (the default; mutually exclusive with -ssh)\n\
\ -ssh : use SSH protocol (requires -user; SSH port must be open on server, and user must have registered a public key)\n\
- \ -remoting : use deprecated Remoting channel protocol (if enabled on server; for compatibility with legacy commands or command modes only)\n\
- \ -i KEY : SSH private key file used for authentication (for use with -ssh or -remoting)\n\
- \ -p HOST:PORT : HTTP proxy host and port for HTTPS proxy tunneling. See https://jenkins.io/redirect/cli-https-proxy-tunnel\n\
+ \ -i KEY : SSH private key file used for authentication (for use with -ssh)\n\
\ -noCertificateCheck : bypass HTTPS certificate check entirely. Use with caution\n\
- \ -noKeyAuth : don't try to load the SSH authentication private key. Conflicts with -i\n\
+ \ -noKeyAuth : don''t try to load the SSH authentication private key. Conflicts with -i\n\
\ -user : specify user (for use with -ssh)\n\
\ -strictHostKey : request strict host key checking (for use with -ssh)\n\
\ -logger FINE : enable detailed logging from the client\n\
\ -auth [ USER:SECRET | @FILE ] : specify username and either password or API token (or load from them both from a file);\n\
\ for use with -http, or -remoting but only when the JNLP agent port is disabled.\n\
- \ Passing crendentials by a file is recommended.\n\
+ \ Passing credentials by file is recommended.\n\
\ See https://jenkins.io/redirect/cli-http-connection-mode for more info and options.\n\
\n\
- The available commands depend on the server. Run the 'help' command to \
+ The available commands depend on the server. Run the ''help'' command to \
see the list.
CLI.NoURL=Neither -s nor the JENKINS_URL env var is specified.
-CLI.VersionMismatch=Version mismatch. This CLI cannot work with this Jenkins server.
CLI.NoSuchFileExists=No such file exists: {0}
CLI.BadAuth=The JENKINS_USER_ID and JENKINS_API_TOKEN env vars should be both set or left empty.
diff --git a/cli/src/test/java/hudson/cli/ConnectionMockTest.java b/cli/src/test/java/hudson/cli/ConnectionMockTest.java
deleted file mode 100644
index 77b319a4d249ee6856bdc60b66e5bd217c3829af..0000000000000000000000000000000000000000
--- a/cli/src/test/java/hudson/cli/ConnectionMockTest.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * The MIT License
- *
- * Copyright (c) 2013 Ericsson
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-package hudson.cli;
-
-import hudson.remoting.FastPipedInputStream;
-import hudson.remoting.FastPipedOutputStream;
-
-import java.io.DataInputStream;
-import java.io.IOException;
-
-import static org.junit.Assert.*;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import static org.powermock.api.mockito.PowerMockito.*;
-
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-
-/**
- * @author marco.miller@ericsson.com
- */
-@RunWith(PowerMockRunner.class)
-@PrepareForTest({Connection.class})
-public class ConnectionMockTest {
-
- @Test
- public void shouldTolerateEmptyByteArrayUponStreamZeroValue() throws IOException {
- DataInputStream din = mock(DataInputStream.class);
- //when(din.readInt()).thenReturn(0) does not work; mock always return 0 for some TBD reason
- Connection c = new Connection(din, new FastPipedOutputStream(new FastPipedInputStream()));
- assertTrue(c.readByteArray().length == 0);
- }
-}
diff --git a/cli/src/test/java/hudson/cli/ConnectionTest.java b/cli/src/test/java/hudson/cli/ConnectionTest.java
deleted file mode 100644
index f4cb0e8b1d595516130553bbe6568f8255324327..0000000000000000000000000000000000000000
--- a/cli/src/test/java/hudson/cli/ConnectionTest.java
+++ /dev/null
@@ -1,87 +0,0 @@
-package hudson.cli;
-
-import static org.junit.Assert.*;
-
-import hudson.remoting.FastPipedInputStream;
-import hudson.remoting.FastPipedOutputStream;
-import org.codehaus.groovy.runtime.Security218;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-
-import javax.crypto.SecretKey;
-import javax.crypto.spec.SecretKeySpec;
-import java.io.IOException;
-
-/**
- * @author Kohsuke Kawaguchi
- */
-public class ConnectionTest {
-
- Throwable e;
- private Connection c1;
- private Connection c2;
-
- @Before
- public void setUp() throws IOException {
- FastPipedInputStream i = new FastPipedInputStream();
- FastPipedInputStream j = new FastPipedInputStream();
-
- c1 = new Connection(i,new FastPipedOutputStream(j));
- c2 = new Connection(j,new FastPipedOutputStream(i));
- }
-
- @Test
- public void testEncrypt() throws Throwable {
- final SecretKey sessionKey = new SecretKeySpec(new byte[16],"AES");
-
- Thread t1 = new Thread() {
- @Override
- public void run() {
- try {
- c1.encryptConnection(sessionKey,"AES/CFB8/NoPadding").writeUTF("Hello");
- } catch (Throwable x) {
- e = x;
- }
- }
- };
- t1.start();
-
- Thread t2 = new Thread() {
- @Override
- public void run() {
- try {
- String data = c2.encryptConnection(sessionKey,"AES/CFB8/NoPadding").readUTF();
- assertEquals("Hello", data);
- } catch (Throwable x) {
- e = x;
- }
- }
- };
- t2.start();
-
- t1.join(9999);
- t2.join(9999);
-
- if (e != null) {
- throw e;
- }
-
- if (t1.isAlive() || t2.isAlive()) {
- t1.interrupt();
- t2.interrupt();
- throw new Error("thread is still alive");
- }
- }
-
- @Test
- public void testSecurity218() throws Exception {
- c1.writeObject(new Security218());
- try {
- c2.readObject();
- fail();
- } catch (SecurityException e) {
- assertTrue(e.getMessage().contains(Security218.class.getName()));
- }
- }
-}
diff --git a/cli/src/test/java/hudson/cli/PrivateKeyProviderTest.java b/cli/src/test/java/hudson/cli/PrivateKeyProviderTest.java
index a29af91e6eb818245140e987dda1c323786a3fd8..1bb49866ba6140ff1f8e27e905dfe4cd6e972862 100644
--- a/cli/src/test/java/hudson/cli/PrivateKeyProviderTest.java
+++ b/cli/src/test/java/hudson/cli/PrivateKeyProviderTest.java
@@ -1,134 +1,104 @@
-/*
- * The MIT License
- *
- * Copyright (c) 2014 Red Hat, Inc.
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
package hudson.cli;
-import static org.mockito.Mockito.verify;
-import static org.powermock.api.mockito.PowerMockito.doReturn;
-import static org.powermock.api.mockito.PowerMockito.mock;
-import static org.powermock.api.mockito.PowerMockito.mockStatic;
-import static org.powermock.api.mockito.PowerMockito.whenNew;
+import org.junit.Test;
import java.io.File;
import java.io.IOException;
-import java.net.URISyntaxException;
-import java.net.URL;
+import java.lang.IllegalArgumentException;
import java.security.GeneralSecurityException;
-import java.security.Key;
import java.security.KeyPair;
-import java.util.Arrays;
+import java.security.NoSuchAlgorithmException;
-import org.hamcrest.Description;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentMatcher;
-import org.mockito.Mockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import static org.junit.Assert.assertNotNull;
-@RunWith(PowerMockRunner.class)
-@PrepareForTest({CLI.class, CLIConnectionFactory.class}) // When mocking new operator caller has to be @PreparedForTest, not class itself
+/**
+keys were generated with ssh-keygen from OpenSSH_7.9p1, LibreSSL 2.7.3
+*/
public class PrivateKeyProviderTest {
+ /**
+ key command: ssh-keygen -f dsa -t dsa -b 1024 -m PEM
+ */
@Test
- public void specifyKeysExplicitly() throws Exception {
- final CLI cli = fakeCLI();
-
- final File dsaKey = keyFile(".ssh/id_dsa");
- final File rsaKey = keyFile(".ssh/id_rsa");
-
- run("-remoting", "-i", dsaKey.getAbsolutePath(), "-i", rsaKey.getAbsolutePath(), "-s", "http://example.com");
-
- verify(cli).authenticate(withKeyPairs(
- keyPair(dsaKey),
- keyPair(rsaKey)
- ));
+ public void loadKeyDSA() throws IOException, GeneralSecurityException {
+ File file = new File(this.getClass().getResource("dsa").getFile());
+ String password = null;
+ KeyPair keyPair = PrivateKeyProvider.loadKey(file, password);
+ assertNotNull(keyPair);
+ assertNotNull(keyPair.getPrivate());
+ assertNotNull(keyPair.getPublic());
}
+ /**
+ key command: ssh-keygen -f dsa-password -t dsa -b 1024 -m PEM -p password
+ */
@Test
- public void useDefaultKeyLocations() throws Exception {
- final CLI cli = fakeCLI();
-
- final File rsaKey = keyFile(".ssh/id_rsa");
- final File dsaKey = keyFile(".ssh/id_dsa");
-
- fakeHome();
- run("-remoting", "-s", "http://example.com");
-
- verify(cli).authenticate(withKeyPairs(
- keyPair(rsaKey),
- keyPair(dsaKey)
- ));
- }
-
- private CLI fakeCLI() throws Exception {
- final CLI cli = mock(CLI.class);
-
- final CLIConnectionFactory factory = mock(CLIConnectionFactory.class, Mockito.CALLS_REAL_METHODS);
- factory.jenkins = new URL("http://example.com");
- doReturn(cli).when(factory).connect();
-
- mockStatic(CLIConnectionFactory.class);
- whenNew(CLIConnectionFactory.class).withNoArguments().thenReturn(factory);
-
- return cli;
+ public void loadKeyDSAPassword() throws IOException, GeneralSecurityException {
+ File file = new File(this.getClass().getResource("dsa-password").getFile());
+ String password = "password";
+ KeyPair keyPair = PrivateKeyProvider.loadKey(file, password);
+ assertNotNull(keyPair);
+ assertNotNull(keyPair.getPrivate());
+ assertNotNull(keyPair.getPublic());
}
-
- private void fakeHome() throws URISyntaxException {
- final File home = new File(this.getClass().getResource(".ssh").toURI()).getParentFile();
- System.setProperty("user.home", home.getAbsolutePath());
+
+ /**
+ key command: ssh-keygen -f rsa -t rsa -b 1024 -m PEM
+ */
+ @Test
+ public void loadKeyRSA() throws IOException, GeneralSecurityException {
+ File file = new File(this.getClass().getResource("rsa").getFile());
+ String password = null;
+ KeyPair keyPair = PrivateKeyProvider.loadKey(file, password);
+ assertNotNull(keyPair);
+ assertNotNull(keyPair.getPrivate());
+ assertNotNull(keyPair.getPublic());
}
- private int run(String... args) throws Exception {
- return CLI._main(args);
+ /**
+ key command: ssh-keygen -f rsa-password -t rsa -b 1024 -m PEM -p password
+ */
+ @Test
+ public void loadKeyRSAPassword() throws IOException, GeneralSecurityException {
+ File file = new File(this.getClass().getResource("rsa-password").getFile());
+ String password = "password";
+ KeyPair keyPair = PrivateKeyProvider.loadKey(file, password);
+ assertNotNull(keyPair);
+ assertNotNull(keyPair.getPrivate());
+ assertNotNull(keyPair.getPublic());
}
-
- private File keyFile(String name) throws URISyntaxException {
- return new File(this.getClass().getResource(name).toURI());
+
+ /**
+ key command: ssh-keygen -f openssh -t rsa -b 1024
+ */
+ @Test
+ public void loadKeyOpenSSH() throws IOException, GeneralSecurityException {
+ File file = new File(this.getClass().getResource("openssh").getFile());
+ String password = null;
+ KeyPair keyPair = PrivateKeyProvider.loadKey(file, password);
+ assertNotNull(keyPair);
+ assertNotNull(keyPair.getPrivate());
+ assertNotNull(keyPair.getPublic());
}
-
- private KeyPair keyPair(File file) throws IOException, GeneralSecurityException {
- return PrivateKeyProvider.loadKey(file, null);
+
+ /**
+ key command: ssh-keygen -f openssh-unsupported -t rsa -b 1024 -p password
+ */
+ @Test(expected = NoSuchAlgorithmException.class)
+ public void loadKeyUnsupportedCipher() throws IOException, GeneralSecurityException {
+ File file = new File(this.getClass().getResource("openssh-unsuported").getFile());
+ String password = "password";
+ PrivateKeyProvider.loadKey(file, password);
}
- private Iterable withKeyPairs(final KeyPair... expected) {
- return Mockito.argThat(new ArgumentMatcher>() {
-
- @Override
- public boolean matches(Iterable actual) {
- int i = 0;
- for (KeyPair akp: actual) {
- if (!eq(expected[i].getPublic(), akp.getPublic())) return false;
- if (!eq(expected[i].getPrivate(), akp.getPrivate())) return false;
- i++;
- }
-
- return i == expected.length;
- }
-
- private boolean eq(final Key expected, final Key actual) {
- return Arrays.equals(expected.getEncoded(), actual.getEncoded());
- }
- });
+ /**
+ key command: ssh-keygen -f openssh -t rsa -b 1024
+ in this key we remove some lines to break the key.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void loadKeyBroken() throws IOException, GeneralSecurityException {
+ File file = new File(this.getClass().getResource("openssh-broken").getFile());
+ String password = "password";
+ PrivateKeyProvider.loadKey(file, password);
}
}
diff --git a/cli/src/test/java/org/codehaus/groovy/runtime/Security218.java b/cli/src/test/java/org/codehaus/groovy/runtime/Security218.java
deleted file mode 100644
index cc3dfeef041c6ac07b91f80d3dc98a5f1530e4c7..0000000000000000000000000000000000000000
--- a/cli/src/test/java/org/codehaus/groovy/runtime/Security218.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package org.codehaus.groovy.runtime;
-
-import java.io.Serializable;
-
-/**
- * Test payload in a prohibited package name.
- *
- * @author Kohsuke Kawaguchi
- */
-public class Security218 implements Serializable {
-}
diff --git a/cli/src/test/resources/hudson/cli/.ssh/id_dsa b/cli/src/test/resources/hudson/cli/.ssh/id_dsa
deleted file mode 100644
index be556daa6d02984101686a02939b6777c780db86..0000000000000000000000000000000000000000
--- a/cli/src/test/resources/hudson/cli/.ssh/id_dsa
+++ /dev/null
@@ -1,12 +0,0 @@
------BEGIN DSA PRIVATE KEY-----
-MIIBugIBAAKBgQCA9mMzB1O52hpObIyaJXgFJQUmc1HV0NEJXsFFGh8U2l0Tkgv4
-fp3MWadiAMmc5H1ot4KQLXl7SwU7dHCCFcGcfQiOjeD5rWeZuHoPAJSDMilcJGE3
-Xo2C+wlescTByEgRRA16vdSlNaDJXKVxq9Wr59G8P4JC6/5EvpeypgYdTQIVAMTf
-aC0O2EGLnJrNBsUdc1s+iUp9AoGAZA7pZYPMJHJWTanJb2DlWHn/QM63jfh38N6W
-ERzmQQks6QdS7UkFlg9cbVGUtn0Yz2SfX3VKiMXNMkAdGD8loBcJS5w6oMMU7rcj
-lldRQ63+fMgdVZYMF5bchC6RhQeGZQ8Imf2iFF28SsE4bi+K12HYgIO5bFxPFUTH
-WSWsMLcCgYBgHJ90ZLU400axB5P0qw/0s4arPD0g53Vzi/Y2h5TJr3KPF2sEIbAc
-2gpFEzUNY0hvH6REKJ+VPPUvlH6ieaXomW8pSGjv4SdxZhJRrDe+Ac/xQse1QdYx
-uWJzpVm3cIGfqLxmQnrklnutI/1F62VZQlq9vjiZL7ir/00vdUTYHwIUUkttGGgl
-a0rWLzPTPF4X4lZfFhk=
------END DSA PRIVATE KEY-----
diff --git a/cli/src/test/resources/hudson/cli/.ssh/id_dsa.pub b/cli/src/test/resources/hudson/cli/.ssh/id_dsa.pub
deleted file mode 100644
index 4a42a845727297abcfa1c1204d0a270392c5a9a6..0000000000000000000000000000000000000000
--- a/cli/src/test/resources/hudson/cli/.ssh/id_dsa.pub
+++ /dev/null
@@ -1 +0,0 @@
-ssh-dss AAAAB3NzaC1kc3MAAACBAID2YzMHU7naGk5sjJoleAUlBSZzUdXQ0QlewUUaHxTaXROSC/h+ncxZp2IAyZzkfWi3gpAteXtLBTt0cIIVwZx9CI6N4PmtZ5m4eg8AlIMyKVwkYTdejYL7CV6xxMHISBFEDXq91KU1oMlcpXGr1avn0bw/gkLr/kS+l7KmBh1NAAAAFQDE32gtDthBi5yazQbFHXNbPolKfQAAAIBkDullg8wkclZNqclvYOVYef9AzreN+Hfw3pYRHOZBCSzpB1LtSQWWD1xtUZS2fRjPZJ9fdUqIxc0yQB0YPyWgFwlLnDqgwxTutyOWV1FDrf58yB1VlgwXltyELpGFB4ZlDwiZ/aIUXbxKwThuL4rXYdiAg7lsXE8VRMdZJawwtwAAAIBgHJ90ZLU400axB5P0qw/0s4arPD0g53Vzi/Y2h5TJr3KPF2sEIbAc2gpFEzUNY0hvH6REKJ+VPPUvlH6ieaXomW8pSGjv4SdxZhJRrDe+Ac/xQse1QdYxuWJzpVm3cIGfqLxmQnrklnutI/1F62VZQlq9vjiZL7ir/00vdUTYHw== ogondza@localhost.localdomain
diff --git a/cli/src/test/resources/hudson/cli/.ssh/id_rsa b/cli/src/test/resources/hudson/cli/.ssh/id_rsa
deleted file mode 100644
index ee2ad6b569a3b5ebbea0d5d1bfcf6ed2bd0dbe27..0000000000000000000000000000000000000000
--- a/cli/src/test/resources/hudson/cli/.ssh/id_rsa
+++ /dev/null
@@ -1,27 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-MIIEpAIBAAKCAQEAyTqwFqp5Ww2Tr/52D7hhdOwgzYGBUqxrOFopa+kjNEL1Yqwb
-+mApUWZ+D3zN9PurhUcVUfeYVXiYWFJ0kG72HIJawL/0BR5oYxRJfumK8Z/sAzAL
-xdhc5O5twETrr9gU3cxtvF5oJNP0I9HickAOeC+ZNpiDIIblrhvxXl/QwqrR+/Gv
-Nb8TApj+rxXEfNp+N69iGnnxzWn1FeKeOAWpwoBAxZNoqBQAFacF7xfQnoygyekC
-xk+ts2O5Zzv8iJ10sVf+x2Q79rxAtsc0xOGhZbBAzbmFTz0PE4iWuo/Vo1c6mM7u
-/dam+FxB2NqPNw7W+4eiCnEVkiQZlrxmuGvK7wIDAQABAoIBACml1+QZDFzoBnUa
-eVzvkFwesvtVnmp5/QcAwinvarXaVedCL9g2JtcOG3EhJ49YtzsyZxs7329xMja1
-eiKalJ157UaPc/XLQVegT0XRGEzCCJrwSr979F39awGsQgt28XqmYN/nui5FH/Z5
-7iAvWc9OKqu+DQWiZc8PQXmC4zYmvhGQ8vKx44RSqlWCjd9IqBVhpE5gxpI/SmCx
-umUNNtoH0hBWr+MsVHzr6UUrC3a99+7bB4We8XMXXFLzbTUSgiYFmK+NxPs/Fux/
-IAyXAMbDw2HeqZ7g4kTaf4cvmVOwhh4zlvB4p7j301LdO1jmvs9z0fn/QJcTpVM7
-ISMKwAECgYEA/uKVdmOKTk3dKzKRFXtWJjqypOXakoX+25lUcVv2PXYRr8Sln9jC
-A13fbhvwq+FqbdnNlB23ag5niCVLfUpB1DYYP5jd4lU8D6HZQiHlmokB6nLT9NIW
-iTcG88E58Bta/l1Ue5Yn+LqluBC4i289wFbH1kZyxQ565s5dJEv9uAECgYEAyhwF
-ZOqTK2lZe5uuN4owVLQaYFj9fsdFHULzlK/UAtkG1gCJhjBmwSEpZFFMH6WgwHk5
-SHJEom0uB4qRv8gQcxl9OSiDsp56ymr0NBhlPVXWr6IzLotLy5XBC1muqvYYlj7E
-kHgSet/h8RUM/FeEiwOFHDU2DkMb8Qx1hfMdAu8CgYBSEsYL9CuB4WK5WTQMlcV8
-0+PYY0dJbSpOrgXZ5sHYsp8pWQn3+cUnbl/WxdpujkxGCR9AdX0tAmxmE5RGSNX/
-rleKiv/PtKB9bCFYQS/83ecnBkioCcpF7tknPm4YmcZoJ8dfcE94sSlRpti11WEu
-AQOiRNcKCwqaLZMib/HIAQKBgQCdiOffeERMYypfgcJzAiCX9WZV0SeOCS7jFwub
-ys17hsSgS/zl/pYpVXrY+dFXHZfGTvcKdB7xaB6nvCfND9lajfSgd+bndEYLvwAo
-Fxfajizv64LvdZ4XytuUyEuwcHBLtBMs9Jqa8iU/8AOWMXVbkdvQV92RkleWNPrp
-9MyZOwKBgQD9x8MnX5LVBfQKuL9qX6l9Da06EyMkzfz3obKn9AAJ3Xj9+45TNPJu
-HnZyvJWesl1vDjXQTm+PVkdyE0WQgoiVX+wxno0hsoly5Uqb5EYHtTUrZzRpkyLK
-1VmtDxT5D8gorUgn6crzk4PKaxRkPfAimZdlkQm6iOtuR3kqn5BtIQ==
------END RSA PRIVATE KEY-----
diff --git a/cli/src/test/resources/hudson/cli/.ssh/id_rsa.pub b/cli/src/test/resources/hudson/cli/.ssh/id_rsa.pub
deleted file mode 100644
index 91f8ff7180e5f05f989b9aec2104d102372e88a4..0000000000000000000000000000000000000000
--- a/cli/src/test/resources/hudson/cli/.ssh/id_rsa.pub
+++ /dev/null
@@ -1 +0,0 @@
-ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDJOrAWqnlbDZOv/nYPuGF07CDNgYFSrGs4Wilr6SM0QvVirBv6YClRZn4PfM30+6uFRxVR95hVeJhYUnSQbvYcglrAv/QFHmhjFEl+6Yrxn+wDMAvF2Fzk7m3AROuv2BTdzG28Xmgk0/Qj0eJyQA54L5k2mIMghuWuG/FeX9DCqtH78a81vxMCmP6vFcR82n43r2IaefHNafUV4p44BanCgEDFk2ioFAAVpwXvF9CejKDJ6QLGT62zY7lnO/yInXSxV/7HZDv2vEC2xzTE4aFlsEDNuYVPPQ8TiJa6j9WjVzqYzu791qb4XEHY2o83Dtb7h6IKcRWSJBmWvGa4a8rv your_email@example.com
diff --git a/cli/src/test/resources/hudson/cli/dsa b/cli/src/test/resources/hudson/cli/dsa
new file mode 100644
index 0000000000000000000000000000000000000000..4f3f64f95d0e0f1ce121fe33a5a00d25361991f5
--- /dev/null
+++ b/cli/src/test/resources/hudson/cli/dsa
@@ -0,0 +1,12 @@
+-----BEGIN DSA PRIVATE KEY-----
+MIIBvAIBAAKBgQCUlgM7pckNS/AU9w80QDlw304vdyK6u6cWXW7F1PFYMhCrsC7C
+ZcbSKo2mTPHk6P77z3zceSAHeYxkXx34N2HYWCth+79N3VIz/EZC/IaN0sea1teF
+XYLvdnDWazDcRdq+3d4iLAL+46QX6tIaEmUh9SFd4kz7P0rwPHz7SP7+MwIVAPnJ
+QmOwaMbyvb0Q0/OjtXF1orn5AoGBAIP6gOIhNErjgnyzHSfAaR/F2wNFW/TR5HFH
+GEnsVuxTXE00mjqZJaDznviDoVtWF/HlKnR6OcnVr7fm61PXKogeLeJ/zgNPWYCe
+7b45iO38Ztnmimmo9GbzTcDYOVDhNhgZEOUWes9Cdrku8yB3Ugf6nN6GY17LoQ9s
+EDk1Fa8TAoGAD3cFbpbnnw0sHaKMRopCPJKgmCtvzJFgf+xGUZRgEPbeB+1UnG2Z
+T8xPyPF4Zwyo0Mf/b0gxjynLpvzLrr3TMQjqjL4vGE1UIx638ff1Jw08tRRCfBIr
+GpIA5AIzZhxRE6aZREN5wqvnWnlEN+s6mOhcQ8gtNOObHaKuGdU+jAwCFQCvo83Q
+shI+la9gd69CWzJx5GJkJw==
+-----END DSA PRIVATE KEY-----
diff --git a/cli/src/test/resources/hudson/cli/dsa-password b/cli/src/test/resources/hudson/cli/dsa-password
new file mode 100644
index 0000000000000000000000000000000000000000..8a28798a8c0d1b8f37ee9d1b15d02c0c311efa44
--- /dev/null
+++ b/cli/src/test/resources/hudson/cli/dsa-password
@@ -0,0 +1,15 @@
+-----BEGIN DSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-128-CBC,CC014AB7E7C376D246B02422C40ED26A
+
+VjIeNQw7EVgiMDJVglpma1CIvRlfzhBBR6r0i5QsVEPTNGLNNUvbqMxUATud8ql0
+Dc84nDj47qbp+jfsvtSfau32hONynT91mNZiCTPUMGFQqiBDMXUMYBv64NXNFi51
++QpI+bW85KLJyRZSmxmBHH31s+6buWwzpYQ2ImwA/Zzkn/1+Evts+VYWEAwXZNBR
+rlpouBiY7XIP3hifU6vBwkH1Dr5Qff0hq13b4T74X2cL2cmWwmPjWDm8nXgDW2Of
+B+5+w2vCp/QDBUpc8LsxNqUK2/B7KA22wqpmQhD7dY4orzaHQWGKwojSd4LwfX6r
+3NEeptKAAn4TusyREypO67g7xUDT0BfZmCNUg6J9dLPWGusx8IvpK/hABzo5Etn/
+CnBRMc1QaDUASHRgzCkadM1DpKmyVQFlMLXCRJaS+51CHGVVGFILcgPsCcrzFL0r
+HOzDbBymhU9Mfw1AygdKNMyZ8NdDsdZ1H3fkkZu7sMgt3PRaYZG+wNImpkdnQDoN
+V3VqTZ5orF4Lo4OqxOuEd7dakpdpyidRvQQc5GaOZuG+2X/NlKhzdGEjt0iubMnX
+S94J+KPJUeVfPphtA2Lb8w==
+-----END DSA PRIVATE KEY-----
diff --git a/cli/src/test/resources/hudson/cli/openssh b/cli/src/test/resources/hudson/cli/openssh
new file mode 100644
index 0000000000000000000000000000000000000000..6d5ce34bdaa8dc9f3e87e499171327ce96938486
--- /dev/null
+++ b/cli/src/test/resources/hudson/cli/openssh
@@ -0,0 +1,17 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAlwAAAAdzc2gtcn
+NhAAAAAwEAAQAAAIEA6YOQ5he3DIIgu+O9HJLvcGlLNbKe04rN7KftKhaALsqJAtS435bk
+xjv/ycn2uMnTapx2Q+Eu/wqATITB+SZEfAkgRMZmTa2Ze3zt/b6rieRhgRgovu3VyXsRnM
+fgCEuJqU5VRW5WlayYRUsJnQTSaeUuJJvQWAeo9TI/DtYzvp8AAAIYX2d44V9neOEAAAAH
+c3NoLXJzYQAAAIEA6YOQ5he3DIIgu+O9HJLvcGlLNbKe04rN7KftKhaALsqJAtS435bkxj
+v/ycn2uMnTapx2Q+Eu/wqATITB+SZEfAkgRMZmTa2Ze3zt/b6rieRhgRgovu3VyXsRnMfg
+CEuJqU5VRW5WlayYRUsJnQTSaeUuJJvQWAeo9TI/DtYzvp8AAAADAQABAAAAgBRXdq7kj/
+iR+WIEs7uifSMwuPGDjtxksg2Uj09kSGRLFmZdu4EWtvUh0uV0J37vbfBSkubU3fAvrP99
+bRxUHhD5Z444BIyht8jlBetfoJOBSE/TQJ/69xguSmHB8XH8/WUqEaNZ2F+q0AAkRt5CTs
+lkML/YJI1mPzy+0ny6tS8hAAAAQQD6N3CByknj5WrDIJQCce+zbhbftnN4RM6OBuaHv4mm
+y0qIAH7i6ZHFqHlr1OnCPzqtzIt4McoyIDEf+9eH3ktKAAAAQQD8uQ/jcs27tMPwb2GYyU
+vSNZ9g225seV+Y1dU+GSk8zuqmkN1Cbc0dmJW8hqncYrbDuFbRH3kPiLTLO3Aifn2xAAAA
+QQDsirzo7Ox+f2SC0TCu6+wbar6f617IhWRlUyPDNa18+ju+BHdw1890sRSpany5RbJekq
+KoumXchRmzl8vZPoVPAAAAHWluaWZjQFRoZS10b3hpYy1hdmVuZ2VyLmxvY2FsAQIDBAU=
+
+-----END OPENSSH PRIVATE KEY-----
diff --git a/cli/src/test/resources/hudson/cli/openssh-broken b/cli/src/test/resources/hudson/cli/openssh-broken
new file mode 100644
index 0000000000000000000000000000000000000000..f4ebcef2d10778153ea17af9d2f1390189f17dc4
--- /dev/null
+++ b/cli/src/test/resources/hudson/cli/openssh-broken
@@ -0,0 +1,16 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAlwAAAAdzc2gtcn
+NhAAAAAwEAAQAAAIEA6YOQ5he3DIIgu+O9HJLvcGlLNbKe04rN7KftKhaALsqJAtS435bk
+xjv/ycn2uMnTapx2Q+Eu/wqATITB+SZEfAkgRMZmTa2Ze3zt/b6rieRhgRgovu3VyXsRnM
+fgCEuJqU5VRW5WlayYRUsJnQTSaeUuJJvQWAeo9TI/DtYzvp8AAAIYX2d44V9neOEAAAAH
+c3NoLXJzYQAAAIEA6YOQ5he3DIIgu+O9HJLvcGlLNbKe04rN7KftKhaALsqJAtS435bkxj
+v/ycn2uMnTapx2Q+Eu/wqATITB+SZEfAkgRMZmTa2Ze3zt/b6rieRhgRgovu3VyXsRnMfg
+CEuJqU5VRW5WlayYRUsJnQTSaeUuJJvQWAeo9TI/DtYzvp8AAAADAQABAAAAgBRXdq7kj/
+iR+WIEs7uifSMwuPGDjtxksg2Uj09kSGRLFmZdu4EWtvUh0uV0J37vbfBSkubU3fAvrP99
+bRxUHhD5Z444BIyht8jlBetfoJOBSE/TQJ/69xguSmHB8XH8/WUqEaNZ2F+q0AAkRt5CTs
+y0qIAH7i6ZHFqHlr1OnCPzqtzIt4McoyIDEf+9eH3ktKAAAAQQD8uQ/jcs27tMPwb2GYyU
+vSNZ9g225seV+Y1dU+GSk8zuqmkN1Cbc0dmJW8hqncYrbDuFbRH3kPiLTLO3Aifn2xAAAA
+QQDsirzo7Ox+f2SC0TCu6+wbar6f617IhWRlUyPDNa18+ju+BHdw1890sRSpany5RbJekq
+KoumXchRmzl8vZPoVPAAAAHWluaWZjQFRoZS10b3hpYy1hdmVuZ2VyLmxvY2FsAQIDBAU=
+
+-----END OPENSSH PRIVATE KEY-----
diff --git a/cli/src/test/resources/hudson/cli/openssh-unsuported b/cli/src/test/resources/hudson/cli/openssh-unsuported
new file mode 100644
index 0000000000000000000000000000000000000000..4ed6878f12c88cf120eb680b5cca53e904ff54cb
--- /dev/null
+++ b/cli/src/test/resources/hudson/cli/openssh-unsuported
@@ -0,0 +1,17 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABDIzkiSdr
+mRRbRY1S2YtBacAAAAEAAAAAEAAACXAAAAB3NzaC1yc2EAAAADAQABAAAAgQCpXxUyU7MR
+b4GrSMnufZb6kaBb1lHBywI7+dBNnpHyq1rpZJRi36lpxxZoaiG+inOeSBh7QPnDVvm2aN
+DdT7V0PFzfZKWzxl2PRSd6EIXUsaaXuqaFcJM7rSqU1EB+9qM9JfGIqkdNKwzGdu3kdJJG
+3VQQUQVtYXAOLIrApslndQAAAiDAF+/lDARNPSMcOlH6uGqfSBszQBwOf22df5yGHLbnpi
+e5yslN+9Z3jFrl2XNDb8sD9e7gZtB/CbkJaDqFCqSOoW979xLLl89jJYle2L48SBbpV7i2
+0/51YYrxs4/75kJUd8uMaOBpNanyI8CBqA5IPmms1NLrSOpAbU20imQNoLUb1B8v2zNBxo
+R64UyU8AkKDMwPmAHwdrM73c7eirAsuVknhg0fFjy4iCxurqz5RO0Hpjf8GHfaCh37gim/
+8f/XqwS+MlAn9KaAGi4hZJFaSuyPPVLmRTS+gleoM0zSt5J+Fvdrs/JnL/XIpf64QZafKZ
+HyiZXnbJKxWhhhpslHz6QoiZ76LxwRS6bP//fsl7KWPY6IUGMD8h6JGi8o6xpsj16xIGUf
+7K+c9TNcjzaO8jtC8ggaixoKdC48LdenfdaagAtBgyWQqxfxTg9ZLiQ2lrZIaF4C0aZi3+
+byeT/mzPyh6aSAsBETjHCbdy/ixi0BNbDB10LlD44J8i9yBROk7WQtM3lJL78Va4KfxSCC
+dsJQPu1ABZEr1SGVqVprUaM43C/VKZk5PjXmkbAy5TCztztiWwa8HtKk4V7sH4G/ENjAtJ
+w2SEybRTx0WNhD9viTSu8z+BqH1mWP2ek/orE0OWU+H6r1zSoBo9scvpUrLC2NYvcUMw+I
+cDupua/5kKQgTzY+kEyRClwvziHEVe4TLZen3UMf2GM3fVC9AUC8
+-----END OPENSSH PRIVATE KEY-----
diff --git a/cli/src/test/resources/hudson/cli/rsa b/cli/src/test/resources/hudson/cli/rsa
new file mode 100644
index 0000000000000000000000000000000000000000..850ecb0e3d710f1477eb482677b041c8de4e5bd9
--- /dev/null
+++ b/cli/src/test/resources/hudson/cli/rsa
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQDcPcvFMiF7OsfaV9MuHQytEOffnyyFraVyYJrgRsdMsFMJcUVJ
+JY2oo2OdMF42CzstSe1fasAZnxalkGBvSdfH6PphgIhBcVj1eCgPR/76q2OyuRk7
+GE5R8GDlQSCsFsV658zyoBXELVFL9HvhXOw/0u7h60h/ertIoYawz44e3wIDAQAB
+AoGBAKTP7bQ07o88DqCbRmJkxL6iPxK+F+A1cPDl0CBzduMxtAIF7LZvTtHa60mP
+D4Fb6D3c67CSvwytW5IsN64wUTNaB6pOXmVnscgsXzNXz4UGsxsEOFUcYkEGJZ8M
+EMKDSqaCI43EMR7nJi/UNrJyj37Axn4cU3bgnDP5DAxO+AK5AkEA7kWA7zw86AoR
+vuSSWEINNSglzTRLZjciDX2b93kh97voRV0q/CHIoSvB3sLU0R/oLpOjaUhPFWrM
+JfI9GQemNQJBAOyg3MHBQhAd1rhXzNkW62903ICjlNGLOTemi+E11iIfGyA0bhLH
+DJQfR+Tx/VLQEVbAMU1pkUg33GRck4SPA0MCQHbmuDCqHrqsS6624VCppW2hWzvL
+nNSlLpkM1YfpKso1OvNiStEHCtdivpwrHYg+I98aTbF8I/rMEJPfDh4vcwECQQCv
+LBbAyNSjIbPHHBhlzXXVOOnTwUV2Kl7dN8ntmvE+qVBncujZtck2DkIm1o32NFnh
+or3c1P3cPJ5HHdGHHGgJAkBdbOMa8VIFBliHr22akFD43gakZ/7ymRNTXA1uj6xt
+NO/1mklbaj4u41UQZU9JjsqebOMGnIc88KMFh6wdfaed
+-----END RSA PRIVATE KEY-----
diff --git a/cli/src/test/resources/hudson/cli/rsa-password b/cli/src/test/resources/hudson/cli/rsa-password
new file mode 100644
index 0000000000000000000000000000000000000000..55c3f7568176f238069be9abc1d01ef4eb9455d5
--- /dev/null
+++ b/cli/src/test/resources/hudson/cli/rsa-password
@@ -0,0 +1,18 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-128-CBC,B937F90AFDFDE86CD9023373F5079F8F
+
+N6ohIfZ+nmqodFzFlFCG6gBXEGIBOl/ojaV+GSHpCJRKJs6lgGSwVmSHESqiKpAI
+ZNQpWEURIVXRpScAuqB+BNQyNjnlFwoo47aBkfDFR2DTY6y+21Z1Fll9ZExABgRJ
+IsMoRVjVbd4eJ1njln73b290EUBNu4ej14pPFsHqSdHQ4atPXw3Alph0NuFYz1CI
+Jg/0vUFFX1u2UjLYAHRU7EKJUk027H1GjZdlV7kcogMRblINokGK0rpmuize6fE9
+WHzEkeGb7qwU4fJ1Sa396TwA5sU6K7xPqV+sArWevavVC4xi0yKoSMmzR2ps5rg+
+XDNBnscNo0dTxnR/Dku2fsqiTsvXZ/LQDWTyTkC0Sx/gYKmfmYfmOpFh6pW0PRUF
+h8Cxm/XkAZV90pK+SrwKA/Pj2vi5nJvAsQlHD+5f7GYLMiB2pWJdbTuXjg0MJjvP
+3TMZm7SqOMe7G4iyXKmlg7Hri2jMPkzvmoGMQZk8bFoX87579w4s0bCdP23lcI2H
+6F7OHTHnPbPc302vN2NWARuqP6XJGRlwJJaNkL8/cvMh42UeVZEZ5WwacUW5g0IN
+IE+K/L5EemBVI2whRsgBBxbyKymBMTolKDhLbGicxAb7E1jr3UXsRv17LaoaPMDN
+TbCjhRkXiYAWXzR/BVwG2vhCI9geCXbLXAVhp2U/z6+KDAVXUIf2Hwz1v1g+fabV
+DGQBzCobfJv9ML9/oO6+KP/dgAA/kBMsLNmQTAR9NslDqdbVujNJ0fSvqaEG1lbq
+7oc36BIyjJgPEehE0kCfzAeVRHq73N8lfbZaz5cnvdMjCXwMubcIL1kBfUpv5eSg
+-----END RSA PRIVATE KEY-----
diff --git a/core/pom.xml b/core/pom.xml
index cbbf3f30616771fbd074ee27f7f709e815ef576c..388f0a7930733d97e6c564d601511bcc3e01f9ca 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -39,7 +39,7 @@ THE SOFTWARE.
true
- 1.255
+ 1.2562.5.6.SEC032.4.12
@@ -63,7 +63,7 @@ THE SOFTWARE.
org.jenkins-civersion-number
- 1.4
+ 1.6org.jenkins-ci
@@ -119,7 +119,7 @@ THE SOFTWARE.
org.jenkins-citrilead-ssh2
- build-217-jenkins-11
+ build-217-jenkins-14org.kohsuke.stapler
@@ -177,6 +177,11 @@ THE SOFTWARE.
teststest
+
+ io.jenkins.stapler
+ jenkins-stapler-support
+ 1.0
+ org.hamcresthamcrest-library
@@ -221,7 +226,7 @@ THE SOFTWARE.
org.jvnet.localizerlocalizer
- 1.24
+ 1.26antlr
@@ -641,7 +646,6 @@ THE SOFTWARE.
org.codehaus.mojobuild-helper-maven-plugin
- 1.7add-source
@@ -774,7 +778,7 @@ THE SOFTWARE.
com.sun.winswwinsw
- 2.1.2
+ 2.2.0binexe${project.build.outputDirectory}/windows-service
diff --git a/core/src/main/java/hudson/AbstractMarkupText.java b/core/src/main/java/hudson/AbstractMarkupText.java
index 1e78b3e10cfadca15f5745415141a6d4d8edf4dc..2398f6f7446972a1f724a0ee85fc58d251f1b22e 100644
--- a/core/src/main/java/hudson/AbstractMarkupText.java
+++ b/core/src/main/java/hudson/AbstractMarkupText.java
@@ -141,7 +141,7 @@ public abstract class AbstractMarkupText {
public List findTokens(Pattern pattern) {
String text = getText();
Matcher m = pattern.matcher(text);
- List r = new ArrayList();
+ List r = new ArrayList<>();
while(m.find()) {
int idx = m.start();
diff --git a/core/src/main/java/hudson/BulkChange.java b/core/src/main/java/hudson/BulkChange.java
index 28b9fde4261074ca2e4828534139d16e18c8f464..e65fa4d3acb6d96705125c78274e129d53352235 100644
--- a/core/src/main/java/hudson/BulkChange.java
+++ b/core/src/main/java/hudson/BulkChange.java
@@ -132,7 +132,7 @@ public class BulkChange implements Closeable {
/**
* {@link BulkChange}s that are effective currently.
*/
- private static final ThreadLocal INSCOPE = new ThreadLocal();
+ private static final ThreadLocal INSCOPE = new ThreadLocal<>();
/**
* Gets the {@link BulkChange} instance currently in scope for the current thread.
diff --git a/core/src/main/java/hudson/ClassicPluginStrategy.java b/core/src/main/java/hudson/ClassicPluginStrategy.java
index 5d173c3839022d162265f7b2a2779c436e30791a..1d93d3fb249cce72a88725bab80d4b9ef523d768 100644
--- a/core/src/main/java/hudson/ClassicPluginStrategy.java
+++ b/core/src/main/java/hudson/ClassicPluginStrategy.java
@@ -23,29 +23,21 @@
*/
package hudson;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-
-import java.io.FileNotFoundException;
-import java.io.InputStream;
-import java.nio.file.Files;
-import java.nio.file.InvalidPathException;
-
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-import jenkins.util.AntWithFindResourceClassLoader;
-import jenkins.util.SystemProperties;
import com.google.common.collect.Lists;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.Plugin.DummyImpl;
import hudson.PluginWrapper.Dependency;
import hudson.model.Hudson;
-import jenkins.util.AntClassLoader;
import hudson.util.CyclicGraphDetector;
import hudson.util.CyclicGraphDetector.CycleDetectedException;
import hudson.util.IOUtils;
import hudson.util.MaskingClassLoader;
-import hudson.util.VersionNumber;
import jenkins.ClassLoaderReflectionToolkit;
import jenkins.ExtensionFilter;
+import jenkins.plugins.DetachedPluginsUtil;
+import jenkins.util.AntClassLoader;
+import jenkins.util.AntWithFindResourceClassLoader;
+import jenkins.util.SystemProperties;
import org.apache.commons.io.output.NullOutputStream;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
@@ -60,37 +52,36 @@ import org.apache.tools.ant.util.GlobPatternMapper;
import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipExtraField;
import org.apache.tools.zip.ZipOutputStream;
+import org.jenkinsci.bytecode.Transformer;
+import javax.annotation.Nonnull;
import java.io.Closeable;
import java.io.File;
+import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.IOException;
+import java.io.InputStream;
import java.net.URL;
-import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.InvalidPathException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
-import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.logging.Level;
import java.util.logging.Logger;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import org.jenkinsci.bytecode.Transformer;
-import org.kohsuke.accmod.Restricted;
-import org.kohsuke.accmod.restrictions.NoExternalUse;
-
-import javax.annotation.Nonnull;
import static org.apache.commons.io.FilenameUtils.getBaseName;
public class ClassicPluginStrategy implements PluginStrategy {
+ private static final Logger LOGGER = Logger.getLogger(ClassicPluginStrategy.class.getName());
+
/**
* Filter for jar files.
*/
@@ -201,7 +192,7 @@ public class ClassicPluginStrategy implements PluginStrategy {
// TODO: define a mechanism to hide classes
// String export = manifest.getMainAttributes().getValue("Export");
- List paths = new ArrayList();
+ List paths = new ArrayList<>();
if (isLinked) {
parseClassPath(manifest, archive, paths, "Libraries", ",");
parseClassPath(manifest, archive, paths, "Class-Path", " +"); // backward compatibility
@@ -227,8 +218,8 @@ public class ClassicPluginStrategy implements PluginStrategy {
}
// compute dependencies
- List dependencies = new ArrayList();
- List optionalDependencies = new ArrayList();
+ List dependencies = new ArrayList<>();
+ List optionalDependencies = new ArrayList<>();
String v = atts.getValue("Plugin-Dependencies");
if (v != null) {
for (String s : v.split(",")) {
@@ -266,32 +257,16 @@ public class ClassicPluginStrategy implements PluginStrategy {
if (jenkinsVersion==null)
jenkinsVersion = atts.getValue("Hudson-Version");
- optionalDependencies.addAll(getImpliedDependencies(pluginName, jenkinsVersion));
+ optionalDependencies.addAll(DetachedPluginsUtil.getImpliedDependencies(pluginName, jenkinsVersion));
}
-
+
/**
- * Returns all the plugin dependencies that are implicit based on a particular Jenkins version
- * @since 2.0
+ * @see DetachedPluginsUtil#getImpliedDependencies(String, String)
*/
+ @Deprecated // since TODO
@Nonnull
public static List getImpliedDependencies(String pluginName, String jenkinsVersion) {
- List out = new ArrayList<>();
- for (DetachedPlugin detached : DETACHED_LIST) {
- // don't fix the dependency for itself, or else we'll have a cycle
- if (detached.shortName.equals(pluginName)) {
- continue;
- }
- if (BREAK_CYCLES.contains(pluginName + ' ' + detached.shortName)) {
- LOGGER.log(Level.FINE, "skipping implicit dependency {0} → {1}", new Object[] {pluginName, detached.shortName});
- continue;
- }
- // some earlier versions of maven-hpi-plugin apparently puts "null" as a literal in Hudson-Version. watch out for them.
- if (jenkinsVersion == null || jenkinsVersion.equals("null") || new VersionNumber(jenkinsVersion).compareTo(detached.splitWhen) <= 0) {
- out.add(new PluginWrapper.Dependency(detached.shortName + ':' + detached.requiredVersion));
- LOGGER.log(Level.FINE, "adding implicit dependency {0} → {1} because of {2}", new Object[] {pluginName, detached.shortName, jenkinsVersion});
- }
- }
- return out;
+ return DetachedPluginsUtil.getImpliedDependencies(pluginName, jenkinsVersion);
}
@Deprecated
@@ -319,140 +294,6 @@ public class ClassicPluginStrategy implements PluginStrategy {
return classLoader;
}
- /**
- * Get the list of all plugins that have ever been {@link DetachedPlugin detached} from Jenkins core.
- * @return A {@link List} of {@link DetachedPlugin}s.
- */
- @Restricted(NoExternalUse.class)
- public static @Nonnull List getDetachedPlugins() {
- return DETACHED_LIST;
- }
-
- /**
- * Get the list of plugins that have been detached since a specific Jenkins release version.
- * @param since The Jenkins version.
- * @return A {@link List} of {@link DetachedPlugin}s.
- */
- @Restricted(NoExternalUse.class)
- public static @Nonnull List getDetachedPlugins(@Nonnull VersionNumber since) {
- List detachedPlugins = new ArrayList<>();
-
- for (DetachedPlugin detachedPlugin : DETACHED_LIST) {
- if (!detachedPlugin.getSplitWhen().isOlderThan(since)) {
- detachedPlugins.add(detachedPlugin);
- }
- }
-
- return detachedPlugins;
- }
-
- /**
- * Is the named plugin a plugin that was detached from Jenkins at some point in the past.
- * @param pluginId The plugin ID.
- * @return {@code true} if the plugin is a plugin that was detached from Jenkins at some
- * point in the past, otherwise {@code false}.
- */
- @Restricted(NoExternalUse.class)
- public static boolean isDetachedPlugin(@Nonnull String pluginId) {
- for (DetachedPlugin detachedPlugin : DETACHED_LIST) {
- if (detachedPlugin.getShortName().equals(pluginId)) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Information about plugins that were originally in the core.
- *
- * A detached plugin is one that has any of the following characteristics:
- *
- *
- * Was an existing plugin that at some time previously bundled with the Jenkins war file.
- *
- *
- * Was previous code in jenkins core that was split to a separate-plugin (but may not have
- * ever been bundled in a jenkins war file - i.e. it gets split after this 2.0 update).
- *
- *
- */
- @Restricted(NoExternalUse.class)
- public static final class DetachedPlugin {
- private final String shortName;
- /**
- * Plugins built for this Jenkins version (and earlier) will automatically be assumed to have
- * this plugin in its dependency.
- *
- * When core/pom.xml version is 1.123-SNAPSHOT when the code is removed, then this value should
- * be "1.123.*" (because 1.124 will be the first version that doesn't include the removed code.)
- */
- private final VersionNumber splitWhen;
- private final String requiredVersion;
-
- private DetachedPlugin(String shortName, String splitWhen, String requiredVersion) {
- this.shortName = shortName;
- this.splitWhen = new VersionNumber(splitWhen);
- this.requiredVersion = requiredVersion;
- }
-
- /**
- * Get the short name of the plugin.
- * @return The short name of the plugin.
- */
- public String getShortName() {
- return shortName;
- }
-
- /**
- * Get the Jenkins version from which the plugin was detached.
- * @return The Jenkins version from which the plugin was detached.
- */
- public VersionNumber getSplitWhen() {
- return splitWhen;
- }
-
- /**
- * Gets the minimum required version for the current version of Jenkins.
- *
- * @return the minimum required version for the current version of Jenkins.
- * @since 2.16
- */
- public VersionNumber getRequiredVersion() {
- return new VersionNumber(requiredVersion);
- }
-
- @Override
- public String toString() {
- return shortName + " " + splitWhen.toString().replace(".*", "") + " " + requiredVersion;
- }
- }
-
- /** Record of which plugins which removed from core and when. */
- private static final List DETACHED_LIST;
-
- /** Implicit dependencies that are known to be unnecessary and which must be cut out to prevent a dependency cycle among bundled plugins. */
- private static final Set BREAK_CYCLES;
-
- static {
- try (InputStream is = ClassicPluginStrategy.class.getResourceAsStream("/jenkins/split-plugins.txt")) {
- DETACHED_LIST = ImmutableList.copyOf(configLines(is).map(line -> {
- String[] pieces = line.split(" ");
- return new DetachedPlugin(pieces[0], pieces[1] + ".*", pieces[2]);
- }).collect(Collectors.toList()));
- } catch (IOException x) {
- throw new ExceptionInInitializerError(x);
- }
- try (InputStream is = ClassicPluginStrategy.class.getResourceAsStream("/jenkins/split-plugin-cycles.txt")) {
- BREAK_CYCLES = ImmutableSet.copyOf(configLines(is).collect(Collectors.toSet()));
- } catch (IOException x) {
- throw new ExceptionInInitializerError(x);
- }
- }
- private static Stream configLines(InputStream is) throws IOException {
- return org.apache.commons.io.IOUtils.readLines(is, StandardCharsets.UTF_8).stream().filter(line -> !line.matches("#.*|\\s*"));
- }
-
/**
* Computes the classloader that takes the class masking into account.
*
@@ -483,7 +324,7 @@ public class ClassicPluginStrategy implements PluginStrategy {
* See {@link ExtensionFinder#scout(Class, Hudson)} for the dead lock issue and what this does.
*/
if (LOGGER.isLoggable(Level.FINER))
- LOGGER.log(Level.FINER,"Scout-loading ExtensionList: "+type, new Throwable());
+ LOGGER.log(Level.FINER, "Scout-loading ExtensionList: "+type, new Throwable());
for (ExtensionFinder finder : finders) {
finder.scout(type, hudson);
}
@@ -495,7 +336,7 @@ public class ClassicPluginStrategy implements PluginStrategy {
} catch (AbstractMethodError e) {
// backward compatibility
for (T t : finder.findExtensions(type, hudson))
- r.add(new ExtensionComponent(t));
+ r.add(new ExtensionComponent<>(t));
}
}
@@ -737,7 +578,7 @@ public class ClassicPluginStrategy implements PluginStrategy {
CyclicGraphDetector cgd = new CyclicGraphDetector() {
@Override
protected List getEdges(PluginWrapper pw) {
- List dep = new ArrayList();
+ List dep = new ArrayList<>();
for (Dependency d : pw.getDependencies()) {
PluginWrapper p = pluginManager.getPlugin(d.shortName);
if (p!=null && p.isActive())
@@ -803,7 +644,7 @@ public class ClassicPluginStrategy implements PluginStrategy {
@SuppressFBWarnings(value = "DMI_COLLECTION_OF_URLS",
justification = "Should not produce network overheads since the URL is local. JENKINS-53793 is a follow-up")
protected Enumeration findResources(String name) throws IOException {
- HashSet result = new HashSet();
+ HashSet result = new HashSet<>();
if (PluginManager.FAST_LOOKUP) {
for (PluginWrapper pw : getTransitiveDependencies()) {
@@ -864,6 +705,5 @@ public class ClassicPluginStrategy implements PluginStrategy {
}
public static boolean useAntClassLoader = SystemProperties.getBoolean(ClassicPluginStrategy.class.getName()+".useAntClassLoader");
- private static final Logger LOGGER = Logger.getLogger(ClassicPluginStrategy.class.getName());
public static boolean DISABLE_TRANSFORMER = SystemProperties.getBoolean(ClassicPluginStrategy.class.getName()+".noBytecodeTransformer");
}
diff --git a/core/src/main/java/hudson/DNSMultiCast.java b/core/src/main/java/hudson/DNSMultiCast.java
index aa71b2e23b2be38ba799c7d3f3742e9442a5c550..fcb3fe33bb05fdffb9bfd7cf54adde74f0e6e51b 100644
--- a/core/src/main/java/hudson/DNSMultiCast.java
+++ b/core/src/main/java/hudson/DNSMultiCast.java
@@ -32,7 +32,7 @@ public class DNSMultiCast implements Closeable {
try {
jmdns = JmDNS.create();
- Map props = new HashMap();
+ Map props = new HashMap<>();
String rootURL = jenkins.getRootUrl();
if (rootURL==null) return null;
diff --git a/core/src/main/java/hudson/DescriptorExtensionList.java b/core/src/main/java/hudson/DescriptorExtensionList.java
index dd334db858be47f551cf25a897d37b11d095fae5..5e9d35f7ec4a3c87f0400bd9737361a7dda6cf7e 100644
--- a/core/src/main/java/hudson/DescriptorExtensionList.java
+++ b/core/src/main/java/hudson/DescriptorExtensionList.java
@@ -76,7 +76,7 @@ public class DescriptorExtensionList, D extends Descrip
if (describableType == (Class) Publisher.class) {
return (DescriptorExtensionList) new Publisher.DescriptorExtensionListImpl(jenkins);
}
- return new DescriptorExtensionList(jenkins,describableType);
+ return new DescriptorExtensionList<>(jenkins, describableType);
}
/**
@@ -161,13 +161,13 @@ public class DescriptorExtensionList, D extends Descrip
@Override
public boolean add(D d) {
boolean r = super.add(d);
- hudson.getExtensionList(Descriptor.class).add(d);
+ getDescriptorExtensionList().add(d);
return r;
}
@Override
public boolean remove(Object o) {
- hudson.getExtensionList(Descriptor.class).remove(o);
+ getDescriptorExtensionList().remove(o);
return super.remove(o);
}
@@ -176,7 +176,8 @@ public class DescriptorExtensionList, D extends Descrip
*/
@Override
protected Object getLoadLock() {
- return this;
+ // Get a lock for the singleton extension list to prevent deadlocks (JENKINS-55361)
+ return getDescriptorExtensionList().getLoadLock();
}
/**
@@ -189,7 +190,7 @@ public class DescriptorExtensionList, D extends Descrip
LOGGER.log(Level.WARNING, "Cannot load extension components, because Jenkins instance has not been assigned yet");
return Collections.emptyList();
}
- return _load(jenkins.getExtensionList(Descriptor.class).getComponents());
+ return _load(getDescriptorExtensionList().getComponents());
}
@Override
@@ -198,7 +199,7 @@ public class DescriptorExtensionList, D extends Descrip
}
private List> _load(Iterable> set) {
- List> r = new ArrayList>();
+ List> r = new ArrayList<>();
for( ExtensionComponent c : set ) {
Descriptor d = c.getInstance();
try {
@@ -211,6 +212,10 @@ public class DescriptorExtensionList, D extends Descrip
return r;
}
+ private ExtensionList getDescriptorExtensionList() {
+ return ExtensionList.lookup(Descriptor.class);
+ }
+
/**
* Stores manually registered Descriptor instances. Keyed by the {@link Describable} type.
*/
diff --git a/core/src/main/java/hudson/EnvVars.java b/core/src/main/java/hudson/EnvVars.java
index cd553f3aba01ea325c58912b990d88aab9cb5e66..8caaebf31290c23a676ceadc9cee171106b86650 100644
--- a/core/src/main/java/hudson/EnvVars.java
+++ b/core/src/main/java/hudson/EnvVars.java
@@ -74,6 +74,7 @@ import javax.annotation.CheckForNull;
* @author Kohsuke Kawaguchi
*/
public class EnvVars extends TreeMap {
+ private static final long serialVersionUID = 4320331661987259022L;
private static Logger LOGGER = Logger.getLogger(EnvVars.class.getName());
/**
* If this {@link EnvVars} object represents the whole environment variable set,
@@ -88,7 +89,7 @@ public class EnvVars extends TreeMap {
/**
* Gets the platform for which these env vars targeted.
- * @since TODO
+ * @since 2.144
* @return The platform.
*/
public @CheckForNull Platform getPlatform() {
@@ -97,7 +98,7 @@ public class EnvVars extends TreeMap {
/**
* Sets the platform for which these env vars target.
- * @since TODO
+ * @since 2.144
* @param platform the platform to set.
*/
public void setPlatform(@Nonnull Platform platform) {
@@ -199,7 +200,7 @@ public class EnvVars extends TreeMap {
}
public void clear() {
- referredVariables = new TreeSet(comparator);
+ referredVariables = new TreeSet<>(comparator);
}
public String resolve(String name) {
@@ -225,8 +226,8 @@ public class EnvVars extends TreeMap {
}
return refereeSetMap.get(n);
}
- };
-
+ }
+
private final Comparator super String> comparator;
@Nonnull
@@ -287,8 +288,8 @@ public class EnvVars extends TreeMap {
* Scan all variables and list all referring variables.
*/
public void scan() {
- refereeSetMap = new TreeMap>(comparator);
- List extendingVariableNames = new ArrayList();
+ refereeSetMap = new TreeMap<>(comparator);
+ List extendingVariableNames = new ArrayList<>();
TraceResolver resolver = new TraceResolver(comparator);
@@ -326,10 +327,10 @@ public class EnvVars extends TreeMap {
// When A refers B, the last appearance of B always comes after
// the last appearance of A.
- List reversedDuplicatedOrder = new ArrayList(sorter.getSorted());
+ List reversedDuplicatedOrder = new ArrayList<>(sorter.getSorted());
Collections.reverse(reversedDuplicatedOrder);
- orderedVariableNames = new ArrayList(overrides.size());
+ orderedVariableNames = new ArrayList<>(overrides.size());
for(String key: reversedDuplicatedOrder) {
if(overrides.containsKey(key) && !orderedVariableNames.contains(key)) {
orderedVariableNames.add(key);
diff --git a/core/src/main/java/hudson/ExpressionFactory2.java b/core/src/main/java/hudson/ExpressionFactory2.java
index 277858c933d5691c4f285a51de3cf8d80dc7c7d1..c8704fccfdb8b06bf0c5b63e1a6fac0969e268cf 100644
--- a/core/src/main/java/hudson/ExpressionFactory2.java
+++ b/core/src/main/java/hudson/ExpressionFactory2.java
@@ -168,5 +168,5 @@ final class ExpressionFactory2 implements ExpressionFactory {
*
* @see Functions#getCurrentJellyContext()
*/
- protected static final ThreadLocal CURRENT_CONTEXT = new ThreadLocal();
+ protected static final ThreadLocal CURRENT_CONTEXT = new ThreadLocal<>();
}
diff --git a/core/src/main/java/hudson/ExtensionComponent.java b/core/src/main/java/hudson/ExtensionComponent.java
index 136d52f81ae7b564db0ca7c1738d34bff73a741c..98e03969f3cc631ad458b9f5b276b9979d108728 100644
--- a/core/src/main/java/hudson/ExtensionComponent.java
+++ b/core/src/main/java/hudson/ExtensionComponent.java
@@ -95,9 +95,7 @@ public class ExtensionComponent implements Comparable>
if (this.instance instanceof Descriptor && that.instance instanceof Descriptor) {
try {
return Util.fixNull(((Descriptor)this.instance).getDisplayName()).compareTo(Util.fixNull(((Descriptor)that.instance).getDisplayName()));
- } catch (RuntimeException x) {
- LOG.log(Level.WARNING, null, x);
- } catch (LinkageError x) {
+ } catch (RuntimeException | LinkageError x) {
LOG.log(Level.WARNING, null, x);
}
}
diff --git a/core/src/main/java/hudson/ExtensionFinder.java b/core/src/main/java/hudson/ExtensionFinder.java
index 1ff0638c10d6b578927ea90757d5acb8bdbeff6b..9e9c18f63b53749471149199d9d5003b41b9b26b 100644
--- a/core/src/main/java/hudson/ExtensionFinder.java
+++ b/core/src/main/java/hudson/ExtensionFinder.java
@@ -42,7 +42,6 @@ import com.google.inject.spi.ProvisionListener;
import hudson.init.InitMilestone;
import hudson.model.Descriptor;
import hudson.model.Hudson;
-import hudson.util.ReflectionUtils;
import jenkins.ExtensionComponentSet;
import jenkins.ExtensionFilter;
import jenkins.ExtensionRefreshException;
diff --git a/core/src/main/java/hudson/ExtensionList.java b/core/src/main/java/hudson/ExtensionList.java
index 566e3de404911db2fbc418bab933a4bdd8261c79..c3f408c4777ca3ced9451299c738b7e91e4e2b06 100644
--- a/core/src/main/java/hudson/ExtensionList.java
+++ b/core/src/main/java/hudson/ExtensionList.java
@@ -86,7 +86,7 @@ public class ExtensionList extends AbstractList implements OnMaster {
@CopyOnWrite
private volatile List> extensions;
- private final List listeners = new CopyOnWriteArrayList();
+ private final List listeners = new CopyOnWriteArrayList<>();
/**
* Place to store manually registered instances with the per-Hudson scope.
@@ -104,7 +104,7 @@ public class ExtensionList extends AbstractList implements OnMaster {
}
protected ExtensionList(Jenkins jenkins, Class extensionType) {
- this(jenkins,extensionType,new CopyOnWriteArrayList>());
+ this(jenkins,extensionType, new CopyOnWriteArrayList<>());
}
/**
@@ -237,7 +237,7 @@ public class ExtensionList extends AbstractList implements OnMaster {
private synchronized boolean removeSync(Object o) {
boolean removed = removeComponent(legacyInstances, o);
if(extensions!=null) {
- List> r = new ArrayList>(extensions);
+ List> r = new ArrayList<>(extensions);
removed |= removeComponent(r,o);
extensions = sort(r);
}
@@ -245,8 +245,7 @@ public class ExtensionList extends AbstractList implements OnMaster {
}
private boolean removeComponent(Collection> collection, Object t) {
- for (Iterator> itr = collection.iterator(); itr.hasNext();) {
- ExtensionComponent c = itr.next();
+ for (ExtensionComponent c : collection) {
if (c.getInstance().equals(t)) {
return collection.remove(c);
}
@@ -280,11 +279,11 @@ public class ExtensionList extends AbstractList implements OnMaster {
}
private synchronized boolean addSync(T t) {
- legacyInstances.add(new ExtensionComponent(t));
+ legacyInstances.add(new ExtensionComponent<>(t));
// if we've already filled extensions, add it
if(extensions!=null) {
- List> r = new ArrayList>(extensions);
- r.add(new ExtensionComponent(t));
+ List> r = new ArrayList<>(extensions);
+ r.add(new ExtensionComponent<>(t));
extensions = sort(r);
}
return true;
@@ -374,8 +373,10 @@ public class ExtensionList extends AbstractList implements OnMaster {
* Loads all the extensions.
*/
protected List> load() {
- if (LOGGER.isLoggable(Level.FINE))
- LOGGER.log(Level.FINE,"Loading ExtensionList: "+extensionType, new Throwable());
+ LOGGER.fine(() -> String.format("Loading ExtensionList '%s'", extensionType.getName()));
+ if (LOGGER.isLoggable(Level.FINER)) {
+ LOGGER.log(Level.FINER, String.format("Loading ExtensionList '%s' from", extensionType.getName()), new Throwable("Only present for stacktrace information"));
+ }
return jenkins.getPluginManager().getPluginStrategy().findComponents(extensionType, hudson);
}
@@ -396,7 +397,7 @@ public class ExtensionList extends AbstractList implements OnMaster {
* The implementation should copy a list, do a sort, and return the new instance.
*/
protected List> sort(List> r) {
- r = new ArrayList>(r);
+ r = new ArrayList<>(r);
Collections.sort(r);
return r;
}
@@ -413,7 +414,7 @@ public class ExtensionList extends AbstractList implements OnMaster {
@SuppressWarnings({"unchecked", "rawtypes"})
public static ExtensionList create(Jenkins jenkins, Class type) {
if(type.getAnnotation(LegacyInstancesAreScopedToHudson.class)!=null)
- return new ExtensionList(jenkins,type);
+ return new ExtensionList<>(jenkins, type);
else {
return new ExtensionList(jenkins, type, staticLegacyInstances.computeIfAbsent(type, key -> new CopyOnWriteArrayList()));
}
diff --git a/core/src/main/java/hudson/ExtensionPoint.java b/core/src/main/java/hudson/ExtensionPoint.java
index e544ed7576ad411694e868a149dbf2ee1a6971b2..f49a5a096cc7ba2b6671b3ec8a0dd5b23746f3a2 100644
--- a/core/src/main/java/hudson/ExtensionPoint.java
+++ b/core/src/main/java/hudson/ExtensionPoint.java
@@ -29,7 +29,6 @@ import static java.lang.annotation.ElementType.TYPE;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Target;
-import jenkins.util.io.OnMaster;
/**
* Marker interface that designates extensible components
diff --git a/core/src/main/java/hudson/FilePath.java b/core/src/main/java/hudson/FilePath.java
index 9452064e8105e124c0e62328f3310c03765b4ff7..e2a4b30b8cd76c6c68f5018d0161273a540ab540 100644
--- a/core/src/main/java/hudson/FilePath.java
+++ b/core/src/main/java/hudson/FilePath.java
@@ -59,7 +59,6 @@ import hudson.util.NamingThreadFactory;
import hudson.util.io.Archiver;
import hudson.util.io.ArchiverFactory;
-import static java.util.logging.Level.FINE;
import java.io.BufferedOutputStream;
import java.io.File;
@@ -80,9 +79,10 @@ import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
+import java.nio.file.FileSystemException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
-import java.nio.file.InvalidPathException;
+import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.LinkOption;
import java.nio.file.StandardCopyOption;
@@ -132,13 +132,12 @@ import org.jenkinsci.remoting.RoleChecker;
import org.jenkinsci.remoting.RoleSensitive;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
+import org.kohsuke.stapler.Function;
import org.kohsuke.stapler.Stapler;
import static hudson.FilePath.TarCompression.GZIP;
-import static hudson.Util.deleteFile;
import static hudson.Util.fileToPath;
import static hudson.Util.fixEmpty;
-import static hudson.Util.isSymlink;
import java.util.Collections;
import org.apache.tools.ant.BuildException;
@@ -326,7 +325,7 @@ public final class FilePath implements Serializable {
}
boolean isAbsolute = buf.length() > 0;
// Split remaining path into tokens, trimming any duplicate or trailing separators
- List tokens = new ArrayList();
+ List tokens = new ArrayList<>();
int s = 0, end = path.length();
for (int i = 0; i < end; i++) {
char c = path.charAt(i);
@@ -387,7 +386,7 @@ public final class FilePath implements Serializable {
return false;
// Windows can handle '/' as a path separator but Unix can't,
// so err on Unix side
- return remote.indexOf("\\")==-1;
+ return !remote.contains("\\");
}
/**
@@ -638,14 +637,13 @@ public final class FilePath implements Serializable {
private void unzip(File dir, File zipFile) throws IOException {
dir = dir.getAbsoluteFile(); // without absolutization, getParentFile below seems to fail
ZipFile zip = new ZipFile(zipFile);
- @SuppressWarnings("unchecked")
Enumeration entries = zip.getEntries();
try {
while (entries.hasMoreElements()) {
ZipEntry e = entries.nextElement();
File f = new File(dir, e.getName());
- if (!f.toPath().normalize().startsWith(dir.toPath())) {
+ if (!f.getCanonicalPath().startsWith(dir.getCanonicalPath())) {
throw new IOException(
"Zip " + zipFile.getPath() + " contains illegal file name that breaks out of the target directory: " + e.getName());
}
@@ -1063,7 +1061,7 @@ public final class FilePath implements Serializable {
if(channel!=null) {
// run this on a remote system
try {
- DelegatingCallable wrapper = new FileCallableWrapper(callable, cl);
+ DelegatingCallable wrapper = new FileCallableWrapper<>(callable, cl);
for (FileCallableWrapperFactory factory : ExtensionList.lookup(FileCallableWrapperFactory.class)) {
wrapper = factory.wrap(wrapper);
}
@@ -1138,7 +1136,7 @@ public final class FilePath implements Serializable {
*/
public Future actAsync(final FileCallable callable) throws IOException, InterruptedException {
try {
- DelegatingCallable wrapper = new FileCallableWrapper(callable);
+ DelegatingCallable wrapper = new FileCallableWrapper<>(callable);
for (FileCallableWrapperFactory factory : ExtensionList.lookup(FileCallableWrapperFactory.class)) {
wrapper = factory.wrap(wrapper);
}
@@ -1269,7 +1267,7 @@ public final class FilePath implements Serializable {
private static final long serialVersionUID = 1L;
@Override
public Void invoke(File f, VirtualChannel channel) throws IOException {
- deleteRecursive(deleting(f));
+ Util.deleteRecursive(fileToPath(f), path -> deleting(path.toFile()));
return null;
}
}
@@ -1284,34 +1282,11 @@ public final class FilePath implements Serializable {
private static final long serialVersionUID = 1L;
@Override
public Void invoke(File f, VirtualChannel channel) throws IOException {
- deleteContentsRecursive(f);
+ Util.deleteContentsRecursive(fileToPath(f), path -> deleting(path.toFile()));
return null;
}
}
- private void deleteRecursive(File dir) throws IOException {
- if(!isSymlink(dir))
- deleteContentsRecursive(dir);
- try {
- deleteFile(deleting(dir));
- } catch (IOException e) {
- // if some of the child directories are big, it might take long enough to delete that
- // it allows others to create new files, causing problems like JENKINS-10113
- // so give it one more attempt before we give up.
- if(!isSymlink(dir))
- deleteContentsRecursive(dir);
- deleteFile(deleting(dir));
- }
- }
-
- private void deleteContentsRecursive(File file) throws IOException {
- File[] files = file.listFiles();
- if(files==null)
- return; // the directory didn't exist in the first place
- for (File child : files)
- deleteRecursive(child);
- }
-
/**
* Gets the file name portion except the extension.
*
@@ -1621,11 +1596,7 @@ public final class FilePath implements Serializable {
@Override
public Void invoke(File f, VirtualChannel channel) throws IOException {
if(!f.exists()) {
- try {
- Files.newOutputStream(creating(f).toPath()).close();
- } catch (InvalidPathException e) {
- throw new IOException(e);
- }
+ Files.newOutputStream(fileToPath(creating(f))).close();
}
if(!stating(f).setLastModified(timestamp))
throw new IOException("Failed to set the timestamp of "+f+" to "+timestamp);
@@ -1868,7 +1839,7 @@ public final class FilePath implements Serializable {
return Collections.emptyList();
}
- ArrayList r = new ArrayList(children.length);
+ ArrayList r = new ArrayList<>(children.length);
for (File child : children)
r.add(new FilePath(child));
@@ -1959,8 +1930,7 @@ public final class FilePath implements Serializable {
} catch (BuildException x) {
throw new IOException(x.getMessage());
}
- String[] files = ds.getIncludedFiles();
- return files;
+ return ds.getIncludedFiles();
}
/**
@@ -1968,11 +1938,7 @@ public final class FilePath implements Serializable {
*/
public InputStream read() throws IOException, InterruptedException {
if(channel==null) {
- try {
- return Files.newInputStream(reading(new File(remote)).toPath());
- } catch (InvalidPathException e) {
- throw new IOException(e);
- }
+ return Files.newInputStream(fileToPath(reading(new File(remote))));
}
final Pipe p = Pipe.createRemoteToLocal();
@@ -1988,11 +1954,9 @@ public final class FilePath implements Serializable {
}
@Override
public Void invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
- try (InputStream fis = Files.newInputStream(reading(f).toPath());
+ try (InputStream fis = Files.newInputStream(fileToPath(reading(f)));
OutputStream out = p.getOut()) {
org.apache.commons.io.IOUtils.copy(fis, out);
- } catch (InvalidPathException e) {
- p.error(new IOException(e));
} catch (Exception x) {
p.error(x);
}
@@ -2063,11 +2027,16 @@ public final class FilePath implements Serializable {
}
/**
- * Reads this file into a string, by using the current system encoding.
+ * Reads this file into a string, by using the current system encoding on the remote machine.
*/
public String readToString() throws IOException, InterruptedException {
- try (InputStream in = read()) {
- return org.apache.commons.io.IOUtils.toString(in);
+ return act(new ReadToString());
+ }
+ private final class ReadToString extends SecureFileCallable {
+ private static final long serialVersionUID = 1L;
+ @Override
+ public String invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
+ return new String(Files.readAllBytes(fileToPath(reading(f))));
}
}
@@ -2090,11 +2059,7 @@ public final class FilePath implements Serializable {
if(channel==null) {
File f = new File(remote).getAbsoluteFile();
mkdirs(f.getParentFile());
- try {
- return Files.newOutputStream(writing(f).toPath());
- } catch (InvalidPathException e) {
- throw new IOException(e);
- }
+ return Files.newOutputStream(fileToPath(writing(f)));
}
return act(new WritePipe());
@@ -2105,12 +2070,7 @@ public final class FilePath implements Serializable {
public OutputStream invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
f = f.getAbsoluteFile();
mkdirs(f.getParentFile());
- try {
- OutputStream fos = Files.newOutputStream(writing(f).toPath());
- return new RemoteOutputStream(fos);
- } catch (InvalidPathException e) {
- throw new IOException(e);
- }
+ return new RemoteOutputStream(Files.newOutputStream(fileToPath(writing(f))));
}
}
@@ -2118,7 +2078,7 @@ public final class FilePath implements Serializable {
* Overwrites this file by placing the given String as the content.
*
* @param encoding
- * Null to use the platform default encoding.
+ * Null to use the platform default encoding on the remote machine.
* @since 1.105
*/
public void write(final String content, final String encoding) throws IOException, InterruptedException {
@@ -2135,11 +2095,9 @@ public final class FilePath implements Serializable {
@Override
public Void invoke(File f, VirtualChannel channel) throws IOException {
mkdirs(f.getParentFile());
- try (OutputStream fos = Files.newOutputStream(writing(f).toPath());
+ try (OutputStream fos = Files.newOutputStream(fileToPath(writing(f)));
Writer w = encoding != null ? new OutputStreamWriter(fos, encoding) : new OutputStreamWriter(fos)) {
w.write(content);
- } catch (InvalidPathException e) {
- throw new IOException(e);
}
return null;
}
@@ -2285,11 +2243,9 @@ public final class FilePath implements Serializable {
}
@Override
public Void invoke(File f, VirtualChannel channel) throws IOException {
- try (InputStream fis = Files.newInputStream(reading(f).toPath())) {
+ try (InputStream fis = Files.newInputStream(fileToPath(reading(f)))) {
org.apache.commons.io.IOUtils.copy(fis, out);
return null;
- } catch (InvalidPathException e) {
- throw new IOException(e);
} finally {
out.close();
}
@@ -2405,12 +2361,7 @@ public final class FilePath implements Serializable {
future.get();
return future2.get();
} catch (ExecutionException e) {
- Throwable cause = e.getCause();
- if (cause == null) cause = e;
- throw cause instanceof IOException
- ? (IOException) cause
- : new IOException(cause)
- ;
+ throw ioWithCause(e);
}
} else {
// remote -> local copy
@@ -2435,15 +2386,20 @@ public final class FilePath implements Serializable {
try {
return future.get();
} catch (ExecutionException e) {
- Throwable cause = e.getCause();
- if (cause == null) cause = e;
- throw cause instanceof IOException
- ? (IOException) cause
- : new IOException(cause)
- ;
+ throw ioWithCause(e);
}
}
}
+
+ private IOException ioWithCause(ExecutionException e) {
+ Throwable cause = e.getCause();
+ if (cause == null) cause = e;
+ return cause instanceof IOException
+ ? (IOException) cause
+ : new IOException(cause)
+ ;
+ }
+
private class CopyRecursiveLocal extends SecureFileCallable {
private final FilePath target;
private final DirScanner scanner;
@@ -3303,36 +3259,91 @@ public final class FilePath implements Serializable {
if (new File(potentialChildRelativePath).isAbsolute()) {
throw new IllegalArgumentException("Only a relative path is supported, the given path is absolute: " + potentialChildRelativePath);
}
+
+ Path parentAbsolutePath = Util.fileToPath(parentFile.getAbsoluteFile());
+ Path parentRealPath;
+ try {
+ if (Functions.isWindows()) {
+ parentRealPath = this.windowsToRealPath(parentAbsolutePath);
+ } else {
+ parentRealPath = parentAbsolutePath.toRealPath();
+ }
+ }
+ catch (NoSuchFileException e) {
+ LOGGER.log(Level.FINE, String.format("Cannot find the real path to the parentFile: %s", parentAbsolutePath), e);
+ return false;
+ }
- Path parent = parentFile.getAbsoluteFile().toPath().normalize();
-
+ // example: "a/b/c" that will become "b/c" then just "c", and finally an empty string
String remainingPath = potentialChildRelativePath;
- File currentFile = parentFile;
+
+ Path currentFilePath = parentFile.toPath();
while (!remainingPath.isEmpty()) {
- File directChild = this.getDirectChild(currentFile, remainingPath);
- File childUsingFullPath = new File(currentFile, remainingPath);
- remainingPath = childUsingFullPath.getAbsolutePath().substring(directChild.getAbsolutePath().length());
-
- File childFileSymbolic = Util.resolveSymlinkToFile(directChild);
+ Path directChild = this.getDirectChild(currentFilePath, remainingPath);
+ Path childUsingFullPath = currentFilePath.resolve(remainingPath);
+ String childUsingFullPathAbs = childUsingFullPath.toAbsolutePath().toString();
+ String directChildAbs = directChild.toAbsolutePath().toString();
+
+ if (childUsingFullPathAbs.length() == directChildAbs.length()) {
+ remainingPath = "";
+ } else {
+ // +1 to avoid the last slash
+ remainingPath = childUsingFullPathAbs.substring(directChildAbs.length() + 1);
+ }
+
+ File childFileSymbolic = Util.resolveSymlinkToFile(directChild.toFile());
if (childFileSymbolic == null) {
- currentFile = directChild;
+ currentFilePath = directChild;
} else {
- currentFile = childFileSymbolic;
+ currentFilePath = childFileSymbolic.toPath();
+ }
+
+ Path currentFileAbsolutePath = currentFilePath.toAbsolutePath();
+ try{
+ Path child = currentFileAbsolutePath.toRealPath();
+ if (!child.startsWith(parentRealPath)) {
+ LOGGER.log(Level.FINE, "Child [{0}] does not start with parent [{1}] => not descendant", new Object[]{ child, parentRealPath });
+ return false;
+ }
+ } catch (NoSuchFileException e) {
+ // nonexistent file / Windows Server 2016 + MSFT docker
+ // in case this folder / file will be copied somewhere else,
+ // it becomes the responsibility of that system to check the isDescendant with the existing links
+ // we are not taking the parentRealPath to avoid possible problem
+ Path child = currentFileAbsolutePath.normalize();
+ Path parent = parentAbsolutePath.normalize();
+ return child.startsWith(parent);
+ } catch(FileSystemException e) {
+ LOGGER.log(Level.WARNING, String.format("Problem during call to the method toRealPath on %s", currentFileAbsolutePath), e);
+ return false;
}
}
- //TODO could be refactored using Util#isDescendant(File, File) from 2.80+
- Path child = currentFile.getAbsoluteFile().toPath().normalize();
- return child.startsWith(parent);
+ return true;
}
- private @CheckForNull File getDirectChild(File parentFile, String childPath){
- File current = new File(parentFile, childPath);
- while (current != null && !parentFile.equals(current.getParentFile())) {
- current = current.getParentFile();
+ private @CheckForNull Path getDirectChild(Path parentPath, String childPath){
+ Path current = parentPath.resolve(childPath);
+ while (current != null && !parentPath.equals(current.getParent())) {
+ current = current.getParent();
}
return current;
}
+
+ private @Nonnull Path windowsToRealPath(@Nonnull Path path) throws IOException {
+ try {
+ return path.toRealPath();
+ }
+ catch (IOException e) {
+ if (LOGGER.isLoggable(Level.FINE)) {
+ LOGGER.log(Level.FINE, String.format("relaxedToRealPath cannot use the regular toRealPath on %s, trying with toRealPath(LinkOption.NOFOLLOW_LINKS)", path), e);
+ }
+ }
+
+ // that's required for specific environment like Windows Server 2016, running MSFT docker
+ // where the root is a
+ return path.toRealPath(LinkOption.NOFOLLOW_LINKS);
+ }
}
private static final SoloFilePathFilter UNRESTRICTED = SoloFilePathFilter.wrap(FilePathFilter.UNRESTRICTED);
diff --git a/core/src/main/java/hudson/FileSystemProvisioner.java b/core/src/main/java/hudson/FileSystemProvisioner.java
index 13f55b7370028832eba66ea7e16af84cb19547aa..901ff79a6aaa61e36cea879ce645250b27a1365a 100644
--- a/core/src/main/java/hudson/FileSystemProvisioner.java
+++ b/core/src/main/java/hudson/FileSystemProvisioner.java
@@ -40,7 +40,6 @@ import org.jenkinsci.Symbol;
import java.io.BufferedOutputStream;
import java.io.File;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
diff --git a/core/src/main/java/hudson/Functions.java b/core/src/main/java/hudson/Functions.java
index ef2a7671a3f777f5f6a4b6c849b40799857ae636..67e9398c231dae3367b4bcc9eba15fd1625922a2 100644
--- a/core/src/main/java/hudson/Functions.java
+++ b/core/src/main/java/hudson/Functions.java
@@ -465,7 +465,7 @@ public class Functions {
}
public static Map getSystemProperties() {
- return new TreeMap