SFTP: Memory leak because AbstractFileProvider#findFileSystem fails to
detect equality of SFTP FileSystemOptions #272

- Normalize File object inputs to absolute Files.
- Add Eclipse-generated hashCode() and equals() methods.
- Fix camel-casing of "passPhrase" -> "passphrase".
- Make defensive copies of byte[]s.
- Normalize Javadoc comments
diff --git a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/sftp/BytesIdentityInfo.java b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/sftp/BytesIdentityInfo.java
index 07b1794..419ecf1 100644
--- a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/sftp/BytesIdentityInfo.java
+++ b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/sftp/BytesIdentityInfo.java
@@ -1,92 +1,126 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.commons.vfs2.provider.sftp;
-
-import com.jcraft.jsch.JSch;
-import com.jcraft.jsch.JSchException;
-
-/**
- * Structure for an identity based on byte arrays.
- *
- * @since 2.4
- */
-public class BytesIdentityInfo implements IdentityProvider {
-
-    private final byte[] passPhrase;
-
-    private final byte[] privateKey;
-
-    private final byte[] publicKey;
-
-    /**
-     * Constructs an identity info with private and passphrase for the private key.
-     *
-     * @param privateKey Private key bytes
-     * @param passPhrase The passphrase to decrypt the private key (can be {@code null} if no passphrase is used)
-     */
-    public BytesIdentityInfo(final byte[] privateKey, final byte[] passPhrase) {
-        this.privateKey = privateKey;
-        this.publicKey = null;
-        this.passPhrase = passPhrase;
-    }
-
-    /**
-     * Constructs an identity info with private and public key and passphrase for the private key.
-     *
-     * @param privateKey Private key bytes
-     * @param publicKey The public key part used for connections with exchange of certificates (can be {@code null})
-     * @param passPhrase The passphrase to decrypt the private key (can be {@code null} if no passphrase is used)
-     */
-    public BytesIdentityInfo(final byte[] privateKey, final byte[] publicKey, final byte[] passPhrase) {
-        this.privateKey = privateKey;
-        this.publicKey = publicKey;
-        this.passPhrase = passPhrase;
-    }
-
-    @Override
-    public void addIdentity(final JSch jsch) throws JSchException {
-        jsch.addIdentity("PrivateKey", privateKey, publicKey, passPhrase);
-    }
-
-    /**
-     * Gets the passphrase.
-     *
-     * @return the passphrase.
-     */
-    public byte[] getPassPhrase() {
-        return passPhrase;
-    }
-
-    /**
-     * Gets the private key.
-     *
-     * @return the private key.
-     */
-    public byte[] getPrivateKeyBytes() {
-        return privateKey;
-    }
-
-    /**
-     * Gets the public key.
-     *
-     * @return the public key.
-     */
-    public byte[] getPublicKeyBytes() {
-        return publicKey;
-    }
-}
+/*

+ * Licensed to the Apache Software Foundation (ASF) under one or more

+ * contributor license agreements.  See the NOTICE file distributed with

+ * this work for additional information regarding copyright ownership.

+ * The ASF licenses this file to You under the Apache License, Version 2.0

+ * (the "License"); you may not use this file except in compliance with

+ * the License.  You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ */

+

+package org.apache.commons.vfs2.provider.sftp;

+

+import java.util.Arrays;

+

+import com.jcraft.jsch.JSch;

+import com.jcraft.jsch.JSchException;

+

+/**

+ * Structure for an identity based on byte arrays.

+ *

+ * @since 2.4

+ */

+public class BytesIdentityInfo implements IdentityProvider {

+

+    private final byte[] passphrase;

+

+    private final byte[] privateKey;

+

+    private final byte[] publicKey;

+

+    /**

+     * Constructs an identity info with private and passphrase for the private key.

+     *

+     * @param privateKey Private key bytes

+     * @param passphrase The passphrase to decrypt the private key (can be {@code null} if no passphrase is used)

+     */

+    public BytesIdentityInfo(final byte[] privateKey, final byte[] passphrase) {

+        this(privateKey, null, passphrase);

+    }

+

+    /**

+     * Constructs an identity info with private and public key and passphrase for the private key.

+     *

+     * @param privateKey Private key bytes

+     * @param publicKey The public key part used for connections with exchange of certificates (can be {@code null})

+     * @param passphrase The passphrase to decrypt the private key (can be {@code null} if no passphrase is used)

+     */

+    public BytesIdentityInfo(final byte[] privateKey, final byte[] publicKey, final byte[] passphrase) {

+        this.privateKey = Utils.clone(privateKey);

+        this.publicKey = Utils.clone(publicKey);

+        this.passphrase = Utils.clone(passphrase);

+    }

+

+    @Override

+    public void addIdentity(final JSch jsch) throws JSchException {

+        jsch.addIdentity("PrivateKey", privateKey, publicKey, passphrase);

+    }

+

+    @Override

+    public boolean equals(final Object obj) {

+        if (this == obj) {

+            return true;

+        }

+        if (!(obj instanceof BytesIdentityInfo)) {

+            return false;

+        }

+        final BytesIdentityInfo other = (BytesIdentityInfo) obj;

+        return Arrays.equals(passphrase, other.passphrase) && Arrays.equals(privateKey, other.privateKey) && Arrays.equals(publicKey, other.publicKey);

+    }

+

+    /**

+     * Gets the passphrase.

+     *

+     * @return the passphrase.

+     * @since 2.10.0

+     */

+    public byte[] getPassphrase() {

+        return Utils.clone(passphrase);

+    }

+

+    /**

+     * Gets the passphrase.

+     *

+     * @return the passphrase.

+     * @deprecated Use {@link #getPassphrase()}.

+     */

+    @Deprecated

+    public byte[] getPassPhrase() {

+        return Utils.clone(passphrase);

+    }

+

+    /**

+     * Gets the private key.

+     *

+     * @return the private key.

+     */

+    public byte[] getPrivateKeyBytes() {

+        return Utils.clone(privateKey);

+    }

+

+    /**

+     * Gets the public key.

+     *

+     * @return the public key.

+     */

+    public byte[] getPublicKeyBytes() {

+        return Utils.clone(publicKey);

+    }

+

+    @Override

+    public int hashCode() {

+        final int prime = 31;

+        int result = 1;

+        result = prime * result + Arrays.hashCode(passphrase);

+        result = prime * result + Arrays.hashCode(privateKey);

+        result = prime * result + Arrays.hashCode(publicKey);

+        return result;

+    }

+}

diff --git a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/sftp/IdentityInfo.java b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/sftp/IdentityInfo.java
index e3319d9..266bc8f 100644
--- a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/sftp/IdentityInfo.java
+++ b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/sftp/IdentityInfo.java
@@ -1,123 +1,162 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.commons.vfs2.provider.sftp;
-
-import java.io.File;
-
-import com.jcraft.jsch.JSch;
-import com.jcraft.jsch.JSchException;
-
-/**
- * Structure for an identity based on Files.
- *
- * @since 2.1
- */
-public class IdentityInfo implements IdentityProvider {
-
-    private final byte[] passPhrase;
-    private final File privateKey;
-    private final File publicKey;
-
-    /**
-     * Constructs an identity info with private key.
-     * <p>
-     * The key is not passphrase protected.
-     * </p>
-     * <p>
-     * We use java.io.File because JSch cannot deal with VFS FileObjects.
-     * </p>
-     *
-     * @param privateKey The file with the private key
-     * @since 2.1
-     */
-    public IdentityInfo(final File privateKey) {
-        this(privateKey, null, null);
-    }
-
-    /**
-     * Constructs an identity info with private key and its passphrase.
-     * <p>
-     * We use java.io.File because JSch cannot deal with VFS FileObjects.
-     * </p>
-     *
-     * @param privateKey The file with the private key
-     * @param passPhrase The passphrase to decrypt the private key (can be {@code null} if no passphrase is used)
-     * @since 2.1
-     */
-    public IdentityInfo(final File privateKey, final byte[] passPhrase) {
-        this(privateKey, null, passPhrase);
-    }
-
-    /**
-     * Constructs an identity info with private and public key and passphrase for the private key.
-     * <p>
-     * We use java.io.File because JSch cannot deal with VFS FileObjects.
-     * </p>
-     *
-     * @param privateKey The file with the private key
-     * @param publicKey  The public key part used for connections with exchange of certificates (can be {@code null})
-     * @param passPhrase The passphrase to decrypt the private key (can be {@code null} if no passphrase is used)
-     * @since 2.1
-     */
-    public IdentityInfo(final File privateKey, final File publicKey, final byte[] passPhrase) {
-        this.privateKey = privateKey;
-        this.publicKey = publicKey;
-        this.passPhrase = passPhrase;
-    }
-
-    /**
-     * @since 2.4
-     */
-    @Override
-    public void addIdentity(final JSch jsch) throws JSchException {
-        jsch.addIdentity(getAbsolutePath(privateKey), getAbsolutePath(publicKey), passPhrase);
-    }
-
-    private String getAbsolutePath(final File file) {
-        return file != null ? file.getAbsolutePath() : null;
-    }
-
-    /**
-     * Get the passphrase of the private key.
-     *
-     * @return the passphrase
-     * @since 2.1
-     */
-    public byte[] getPassPhrase() {
-        return passPhrase;
-    }
-
-    /**
-     * Get the file with the private key.
-     *
-     * @return the file
-     * @since 2.1
-     */
-    public File getPrivateKey() {
-        return privateKey;
-    }
-
-    /**
-     * Get the file with the public key.
-     *
-     * @return the file
-     * @since 2.1
-     */
-    public File getPublicKey() {
-        return publicKey;
-    }
-}
+/*

+ * Licensed to the Apache Software Foundation (ASF) under one or more

+ * contributor license agreements.  See the NOTICE file distributed with

+ * this work for additional information regarding copyright ownership.

+ * The ASF licenses this file to You under the Apache License, Version 2.0

+ * (the "License"); you may not use this file except in compliance with

+ * the License.  You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ */

+package org.apache.commons.vfs2.provider.sftp;

+

+import java.io.File;

+import java.util.Arrays;

+import java.util.Objects;

+

+import com.jcraft.jsch.JSch;

+import com.jcraft.jsch.JSchException;

+

+/**

+ * Structure for an identity based on Files.

+ *

+ * @since 2.1

+ */

+public class IdentityInfo implements IdentityProvider {

+

+    private final byte[] passphrase;

+    private final File privateKey;

+    private final File publicKey;

+

+    /**

+     * Constructs an identity info with private key.

+     * <p>

+     * The key is not passphrase protected.

+     * </p>

+     * <p>

+     * We use java.io.File because JSch cannot deal with VFS FileObjects.

+     * </p>

+     *

+     * @param privateKey The file with the private key

+     * @since 2.1

+     */

+    public IdentityInfo(final File privateKey) {

+        this(privateKey, null, null);

+    }

+

+    /**

+     * Constructs an identity info with private key and its passphrase.

+     * <p>

+     * We use java.io.File because JSch cannot deal with VFS FileObjects.

+     * </p>

+     *

+     * @param privateKey The file with the private key

+     * @param passphrase The passphrase to decrypt the private key (can be {@code null} if no passphrase is used)

+     * @since 2.1

+     */

+    public IdentityInfo(final File privateKey, final byte[] passphrase) {

+        this(privateKey, null, passphrase);

+    }

+

+    /**

+     * Constructs an identity info with private and public key and passphrase for the private key.

+     * <p>

+     * We use java.io.File because JSch cannot deal with VFS FileObjects.

+     * </p>

+     *

+     * @param privateKey The file with the private key

+     * @param publicKey  The public key part used for connections with exchange of certificates (can be {@code null})

+     * @param passphrase The passphrase to decrypt the private key (can be {@code null} if no passphrase is used)

+     * @since 2.1

+     */

+    public IdentityInfo(final File privateKey, final File publicKey, final byte[] passphrase) {

+        this.privateKey = getAbsoluteFile(privateKey);

+        this.publicKey = getAbsoluteFile(publicKey);

+        this.passphrase = Utils.clone(passphrase);

+    }

+

+    /**

+     * @since 2.4

+     */

+    @Override

+    public void addIdentity(final JSch jsch) throws JSchException {

+        jsch.addIdentity(getAbsolutePath(privateKey), getAbsolutePath(publicKey), passphrase);

+    }

+

+    @Override

+    public boolean equals(Object obj) {

+        if (this == obj) {

+            return true;

+        }

+        if (!(obj instanceof IdentityInfo)) {

+            return false;

+        }

+        IdentityInfo other = (IdentityInfo) obj;

+        return Arrays.equals(passphrase, other.passphrase) && Objects.equals(privateKey, other.privateKey) && Objects.equals(publicKey, other.publicKey);

+    }

+

+    private File getAbsoluteFile(final File privateKey) {

+        return privateKey != null ? privateKey.getAbsoluteFile() : null;

+    }

+

+    private String getAbsolutePath(final File file) {

+        return file != null ? file.getAbsolutePath() : null;

+    }

+

+    /**

+     * Gets the passphrase of the private key.

+     *

+     * @return the passphrase

+     * @since 2.10.0

+     */

+    public byte[] getPassphrase() {

+        return Utils.clone(passphrase);

+    }

+

+    /**

+     * Gets the passphrase of the private key.

+     *

+     * @return the passphrase

+     * @since 2.1

+     * @deprecated Use {@link #getPassphrase()}.

+     */

+    @Deprecated

+    public byte[] getPassPhrase() {

+        return Utils.clone(passphrase);

+    }

+

+    /**

+     * Gets the file with the private key.

+     *

+     * @return the file

+     * @since 2.1

+     */

+    public File getPrivateKey() {

+        return privateKey;

+    }

+

+    /**

+     * Gets the file with the public key.

+     *

+     * @return the file

+     * @since 2.1

+     */

+    public File getPublicKey() {

+        return publicKey;

+    }

+

+    @Override

+    public int hashCode() {

+        final int prime = 31;

+        int result = 1;

+        result = prime * result + Arrays.hashCode(passphrase);

+        result = prime * result + Objects.hash(privateKey, publicKey);

+        return result;

+    }

+}

diff --git a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/sftp/Utils.java b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/sftp/Utils.java
new file mode 100644
index 0000000..d84b0ef
--- /dev/null
+++ b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/sftp/Utils.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.vfs2.provider.sftp;
+
+final class Utils {
+
+    private Utils() {
+        // never
+    }
+
+    static byte[] clone(final byte[] array) {
+        return array != null ? array.clone() : null;
+    }
+
+}