You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by lg...@apache.org on 2017/12/28 09:06:46 UTC
[1/3] mina-sshd git commit: [SSHD-790] Allow users to register a
custom SFTP client factory
Repository: mina-sshd
Updated Branches:
refs/heads/master c5b163f2b -> 154287462
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/DefaultSftpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/DefaultSftpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/DefaultSftpClient.java
new file mode 100644
index 0000000..4118ff6
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/DefaultSftpClient.java
@@ -0,0 +1,462 @@
+/*
+ * 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.sshd.client.subsystem.sftp.impl;
+
+import java.io.ByteArrayOutputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.io.StreamCorruptedException;
+import java.net.SocketTimeoutException;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.Objects;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.sshd.client.channel.ChannelSubsystem;
+import org.apache.sshd.client.channel.ClientChannel;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.client.subsystem.sftp.SftpVersionSelector;
+import org.apache.sshd.common.PropertyResolverUtils;
+import org.apache.sshd.common.SshException;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.subsystem.sftp.extensions.ParserUtils;
+import org.apache.sshd.common.subsystem.sftp.extensions.VersionsParser.Versions;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.common.util.buffer.BufferUtils;
+import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class DefaultSftpClient extends AbstractSftpClient {
+ private final ClientSession clientSession;
+ private final ChannelSubsystem channel;
+ private final Map<Integer, Buffer> messages = new HashMap<>();
+ private final AtomicInteger cmdId = new AtomicInteger(100);
+ private final Buffer receiveBuffer = new ByteArrayBuffer();
+ private final byte[] workBuf = new byte[Integer.BYTES];
+ private final AtomicInteger versionHolder = new AtomicInteger(0);
+ private final AtomicBoolean closing = new AtomicBoolean(false);
+ private final NavigableMap<String, byte[]> extensions = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ private final NavigableMap<String, byte[]> exposedExtensions = Collections.unmodifiableNavigableMap(extensions);
+ private Charset nameDecodingCharset = DEFAULT_NAME_DECODING_CHARSET;
+
+ public DefaultSftpClient(ClientSession clientSession) throws IOException {
+ this.nameDecodingCharset = PropertyResolverUtils.getCharset(clientSession, NAME_DECODING_CHARSET, DEFAULT_NAME_DECODING_CHARSET);
+ this.clientSession = Objects.requireNonNull(clientSession, "No client session");
+ this.channel = clientSession.createSubsystemChannel(SftpConstants.SFTP_SUBSYSTEM_NAME);
+ this.channel.setOut(new OutputStream() {
+ private final byte[] singleByte = new byte[1];
+ @Override
+ public void write(int b) throws IOException {
+ synchronized (singleByte) {
+ singleByte[0] = (byte) b;
+ write(singleByte);
+ }
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ data(b, off, len);
+ }
+ });
+ this.channel.setErr(new ByteArrayOutputStream(Byte.MAX_VALUE));
+
+ long initializationTimeout = clientSession.getLongProperty(SFTP_CHANNEL_OPEN_TIMEOUT, DEFAULT_CHANNEL_OPEN_TIMEOUT);
+ this.channel.open().verify(initializationTimeout);
+ this.channel.onClose(() -> {
+ synchronized (messages) {
+ closing.set(true);
+ messages.notifyAll();
+ }
+
+ if (versionHolder.get() <= 0) {
+ log.warn("onClose({}) closed before version negotiated", channel);
+ }
+ });
+
+ try {
+ init(initializationTimeout);
+ } catch (IOException | RuntimeException e) {
+ this.channel.close(true);
+ throw e;
+ }
+ }
+
+ @Override
+ public int getVersion() {
+ return versionHolder.get();
+ }
+
+ @Override
+ public ClientSession getClientSession() {
+ return clientSession;
+ }
+
+ @Override
+ public ClientChannel getClientChannel() {
+ return channel;
+ }
+
+ @Override
+ public NavigableMap<String, byte[]> getServerExtensions() {
+ return exposedExtensions;
+ }
+
+ @Override
+ public Charset getNameDecodingCharset() {
+ return nameDecodingCharset;
+ }
+
+ @Override
+ public void setNameDecodingCharset(Charset nameDecodingCharset) {
+ this.nameDecodingCharset = Objects.requireNonNull(nameDecodingCharset, "No charset provided");
+ }
+
+ @Override
+ public boolean isClosing() {
+ return closing.get();
+ }
+
+ @Override
+ public boolean isOpen() {
+ return this.channel.isOpen();
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (isOpen()) {
+ this.channel.close(false);
+ }
+ }
+
+ /**
+ * Receive binary data
+ * @param buf The buffer for the incoming data
+ * @param start Offset in buffer to place the data
+ * @param len Available space in buffer for the data
+ * @return Actual size of received data
+ * @throws IOException If failed to receive incoming data
+ */
+ protected int data(byte[] buf, int start, int len) throws IOException {
+ Buffer incoming = new ByteArrayBuffer(buf, start, len);
+ // If we already have partial data, we need to append it to the buffer and use it
+ if (receiveBuffer.available() > 0) {
+ receiveBuffer.putBuffer(incoming);
+ incoming = receiveBuffer;
+ }
+
+ // Process commands
+ int rpos = incoming.rpos();
+ for (int count = 1; receive(incoming); count++) {
+ if (log.isTraceEnabled()) {
+ log.trace("data({}) Processed {} data messages", getClientChannel(), count);
+ }
+ }
+
+ int read = incoming.rpos() - rpos;
+ // Compact and add remaining data
+ receiveBuffer.compact();
+ if (receiveBuffer != incoming && incoming.available() > 0) {
+ receiveBuffer.putBuffer(incoming);
+ }
+
+ return read;
+ }
+
+ /**
+ * Read SFTP packets from buffer
+ *
+ * @param incoming The received {@link Buffer}
+ * @return {@code true} if data from incoming buffer was processed
+ * @throws IOException if failed to process the buffer
+ * @see #process(Buffer)
+ */
+ protected boolean receive(Buffer incoming) throws IOException {
+ int rpos = incoming.rpos();
+ int wpos = incoming.wpos();
+ ClientSession session = getClientSession();
+ session.resetIdleTimeout();
+
+ if ((wpos - rpos) > 4) {
+ int length = incoming.getInt();
+ if (length < 5) {
+ throw new IOException("Illegal sftp packet length: " + length);
+ }
+ if ((wpos - rpos) >= (length + 4)) {
+ incoming.rpos(rpos);
+ incoming.wpos(rpos + 4 + length);
+ process(incoming);
+ incoming.rpos(rpos + 4 + length);
+ incoming.wpos(wpos);
+ return true;
+ }
+ }
+ incoming.rpos(rpos);
+ return false;
+ }
+
+ /**
+ * Process an SFTP packet
+ *
+ * @param incoming The received {@link Buffer}
+ * @throws IOException if failed to process the buffer
+ */
+ protected void process(Buffer incoming) throws IOException {
+ // create a copy of the buffer in case it is being re-used
+ Buffer buffer = new ByteArrayBuffer(incoming.available() + Long.SIZE, false);
+ buffer.putBuffer(incoming);
+
+ int rpos = buffer.rpos();
+ int length = buffer.getInt();
+ int type = buffer.getUByte();
+ Integer id = buffer.getInt();
+ buffer.rpos(rpos);
+
+ if (log.isTraceEnabled()) {
+ log.trace("process({}) id={}, type={}, len={}",
+ getClientChannel(), id, SftpConstants.getCommandMessageName(type), length);
+ }
+
+ synchronized (messages) {
+ messages.put(id, buffer);
+ messages.notifyAll();
+ }
+ }
+
+ @Override
+ public int send(int cmd, Buffer buffer) throws IOException {
+ int id = cmdId.incrementAndGet();
+ int len = buffer.available();
+ if (log.isTraceEnabled()) {
+ log.trace("send({}) cmd={}, len={}, id={}",
+ getClientChannel(), SftpConstants.getCommandMessageName(cmd), len, id);
+ }
+
+ OutputStream dos = channel.getInvertedIn();
+ BufferUtils.writeInt(dos, 1 /* cmd */ + Integer.BYTES /* id */ + len, workBuf);
+ dos.write(cmd & 0xFF);
+ BufferUtils.writeInt(dos, id, workBuf);
+ dos.write(buffer.array(), buffer.rpos(), len);
+ dos.flush();
+ return id;
+ }
+
+ @Override
+ public Buffer receive(int id) throws IOException {
+ Integer reqId = id;
+ synchronized (messages) {
+ for (int count = 1;; count++) {
+ if (isClosing() || (!isOpen())) {
+ throw new SshException("Channel is being closed");
+ }
+
+ Buffer buffer = messages.remove(reqId);
+ if (buffer != null) {
+ return buffer;
+ }
+
+ try {
+ messages.wait();
+ } catch (InterruptedException e) {
+ throw (IOException) new InterruptedIOException("Interrupted while waiting for messages at iteration #" + count).initCause(e);
+ }
+ }
+ }
+ }
+
+ protected Buffer read() throws IOException {
+ InputStream dis = channel.getInvertedOut();
+ int length = BufferUtils.readInt(dis, workBuf);
+ // must have at least command + length
+ if (length < (1 + Integer.BYTES)) {
+ throw new IllegalArgumentException("Bad length: " + length);
+ }
+
+ Buffer buffer = new ByteArrayBuffer(length + Integer.BYTES, false);
+ buffer.putInt(length);
+ int nb = length;
+ while (nb > 0) {
+ int readLen = dis.read(buffer.array(), buffer.wpos(), nb);
+ if (readLen < 0) {
+ throw new IllegalArgumentException("Premature EOF while read " + length + " bytes - remaining=" + nb);
+ }
+ buffer.wpos(buffer.wpos() + readLen);
+ nb -= readLen;
+ }
+
+ return buffer;
+ }
+
+ protected void init(long initializationTimeout) throws IOException {
+ ValidateUtils.checkTrue(initializationTimeout > 0L, "Invalid initialization timeout: %d", initializationTimeout);
+
+ // Send init packet
+ OutputStream dos = channel.getInvertedIn();
+ BufferUtils.writeInt(dos, 5 /* total length */, workBuf);
+ dos.write(SftpConstants.SSH_FXP_INIT);
+ BufferUtils.writeInt(dos, SftpConstants.SFTP_V6, workBuf);
+ dos.flush();
+
+ Buffer buffer;
+ Integer reqId;
+ synchronized (messages) {
+ /*
+ * We need to use a timeout since if the remote server does not support
+ * SFTP, we will not know it immediately. This is due to the fact that the
+ * request for the subsystem does not contain a reply as to its success or
+ * failure. Thus, the SFTP channel is created by the client, but there is
+ * no one on the other side to reply - thus the need for the timeout
+ */
+ for (long remainingTimeout = initializationTimeout; (remainingTimeout > 0L) && messages.isEmpty() && (!isClosing()) && isOpen();) {
+ try {
+ long sleepStart = System.nanoTime();
+ messages.wait(remainingTimeout);
+ long sleepEnd = System.nanoTime();
+ long sleepDuration = sleepEnd - sleepStart;
+ long sleepMillis = TimeUnit.NANOSECONDS.toMillis(sleepDuration);
+ if (sleepMillis < 1L) {
+ remainingTimeout--;
+ } else {
+ remainingTimeout -= sleepMillis;
+ }
+ } catch (InterruptedException e) {
+ throw (IOException) new InterruptedIOException("Interrupted init()").initCause(e);
+ }
+ }
+
+ if (isClosing() || (!isOpen())) {
+ throw new EOFException("Closing while await init message");
+ }
+
+ if (messages.isEmpty()) {
+ throw new SocketTimeoutException("No incoming initialization response received within " + initializationTimeout + " msec.");
+ }
+
+ Collection<Integer> ids = messages.keySet();
+ Iterator<Integer> iter = ids.iterator();
+ reqId = iter.next();
+ buffer = messages.remove(reqId);
+ }
+
+ int length = buffer.getInt();
+ int type = buffer.getUByte();
+ int id = buffer.getInt();
+ if (log.isTraceEnabled()) {
+ log.trace("init({}) id={} type={} len={}",
+ getClientChannel(), id, SftpConstants.getCommandMessageName(type), length);
+ }
+
+ if (type == SftpConstants.SSH_FXP_VERSION) {
+ if (id < SftpConstants.SFTP_V3) {
+ throw new SshException("Unsupported sftp version " + id);
+ }
+ versionHolder.set(id);
+
+ if (log.isTraceEnabled()) {
+ log.trace("init({}) version={}", getClientChannel(), versionHolder);
+ }
+
+ while (buffer.available() > 0) {
+ String name = buffer.getString();
+ byte[] data = buffer.getBytes();
+ if (log.isTraceEnabled()) {
+ log.trace("init({}) added extension=", getClientChannel(), name);
+ }
+ extensions.put(name, data);
+ }
+ } else if (type == SftpConstants.SSH_FXP_STATUS) {
+ int substatus = buffer.getInt();
+ String msg = buffer.getString();
+ String lang = buffer.getString();
+ if (log.isTraceEnabled()) {
+ log.trace("init({})[id={}] - status: {} [{}] {}",
+ getClientChannel(), id, SftpConstants.getStatusName(substatus), lang, msg);
+ }
+
+ throwStatusException(SftpConstants.SSH_FXP_INIT, id, substatus, msg, lang);
+ } else {
+ handleUnexpectedPacket(SftpConstants.SSH_FXP_INIT, SftpConstants.SSH_FXP_VERSION, id, type, length, buffer);
+ }
+ }
+
+ /**
+ * @param selector The {@link SftpVersionSelector} to use - ignored if {@code null}
+ * @return The selected version (may be same as current)
+ * @throws IOException If failed to negotiate
+ */
+ public int negotiateVersion(SftpVersionSelector selector) throws IOException {
+ int current = getVersion();
+ if (selector == null) {
+ return current;
+ }
+
+ Set<Integer> available = GenericUtils.asSortedSet(Collections.singleton(current));
+ Map<String, ?> parsed = getParsedServerExtensions();
+ Collection<String> extensions = ParserUtils.supportedExtensions(parsed);
+ if ((GenericUtils.size(extensions) > 0) && extensions.contains(SftpConstants.EXT_VERSION_SELECT)) {
+ Versions vers = GenericUtils.isEmpty(parsed) ? null : (Versions) parsed.get(SftpConstants.EXT_VERSIONS);
+ Collection<String> reported = (vers == null) ? null : vers.getVersions();
+ if (GenericUtils.size(reported) > 0) {
+ for (String v : reported) {
+ if (!available.add(Integer.valueOf(v))) {
+ continue; // debug breakpoint
+ }
+ }
+ }
+ }
+
+ int selected = selector.selectVersion(getClientSession(), current, new ArrayList<>(available));
+ if (log.isDebugEnabled()) {
+ log.debug("negotiateVersion({}) current={} {} -> {}", getClientChannel(), current, available, selected);
+ }
+
+ if (selected == current) {
+ return current;
+ }
+
+ if (!available.contains(selected)) {
+ throw new StreamCorruptedException("Selected version (" + selected + ") not part of available: " + available);
+ }
+
+ String verVal = String.valueOf(selected);
+ Buffer buffer = new ByteArrayBuffer(Integer.BYTES + SftpConstants.EXT_VERSION_SELECT.length() // extension name
+ + Integer.BYTES + verVal.length() + Byte.SIZE, false);
+ buffer.putString(SftpConstants.EXT_VERSION_SELECT);
+ buffer.putString(verVal);
+ checkCommandStatus(SftpConstants.SSH_FXP_EXTENDED, buffer);
+ versionHolder.set(selected);
+ return selected;
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/DefaultSftpClientFactory.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/DefaultSftpClientFactory.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/DefaultSftpClientFactory.java
new file mode 100644
index 0000000..c6702f8
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/DefaultSftpClientFactory.java
@@ -0,0 +1,81 @@
+/*
+ * 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.sshd.client.subsystem.sftp.impl;
+
+import java.io.IOException;
+
+import org.apache.sshd.client.ClientFactoryManager;
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpClientFactory;
+import org.apache.sshd.client.subsystem.sftp.SftpFileSystem;
+import org.apache.sshd.client.subsystem.sftp.SftpFileSystemProvider;
+import org.apache.sshd.client.subsystem.sftp.SftpVersionSelector;
+import org.apache.sshd.common.util.logging.AbstractLoggingBean;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class DefaultSftpClientFactory extends AbstractLoggingBean implements SftpClientFactory {
+ public static final DefaultSftpClientFactory INSTANCE = new DefaultSftpClientFactory();
+
+ public DefaultSftpClientFactory() {
+ super();
+ }
+
+ @Override
+ public SftpClient createSftpClient(ClientSession session, SftpVersionSelector selector) throws IOException {
+ DefaultSftpClient client = createDefaultSftpClient(session, selector);
+ try {
+ client.negotiateVersion(selector);
+ } catch (IOException | RuntimeException e) {
+ if (log.isDebugEnabled()) {
+ log.debug("createSftpClient({}) failed ({}) to negotiate version: {}",
+ session, e.getClass().getSimpleName(), e.getMessage());
+ }
+ if (log.isTraceEnabled()) {
+ log.trace("createSftpClient(" + session + ") version negotiation failure details", e);
+ }
+
+ client.close();
+ throw e;
+ }
+
+ return client;
+ }
+
+ protected DefaultSftpClient createDefaultSftpClient(ClientSession session, SftpVersionSelector selector) throws IOException {
+ return new DefaultSftpClient(session);
+ }
+
+ @Override
+ public SftpFileSystem createSftpFileSystem(
+ ClientSession session, SftpVersionSelector selector, int readBufferSize, int writeBufferSize)
+ throws IOException {
+ ClientFactoryManager manager = session.getFactoryManager();
+ SftpFileSystemProvider provider = new SftpFileSystemProvider((SshClient) manager, selector);
+ SftpFileSystem fs = provider.newFileSystem(session);
+ fs.setReadBufferSize(readBufferSize);
+ fs.setWriteBufferSize(writeBufferSize);
+ return fs;
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandleTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandleTest.java b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandleTest.java
index eee30c5..35c325b 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandleTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandleTest.java
@@ -25,6 +25,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.sshd.client.subsystem.sftp.SftpClient.CloseableHandle;
import org.apache.sshd.client.subsystem.sftp.SftpClient.Handle;
+import org.apache.sshd.client.subsystem.sftp.impl.DefaultCloseableHandle;
import org.apache.sshd.util.test.BaseTestSupport;
import org.junit.FixMethodOrder;
import org.junit.Test;
[2/3] mina-sshd git commit: [SSHD-790] Allow users to register a
custom SFTP client factory
Posted by lg...@apache.org.
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultSftpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultSftpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultSftpClient.java
deleted file mode 100644
index 0788b67..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultSftpClient.java
+++ /dev/null
@@ -1,461 +0,0 @@
-/*
- * 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.sshd.client.subsystem.sftp;
-
-import java.io.ByteArrayOutputStream;
-import java.io.EOFException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InterruptedIOException;
-import java.io.OutputStream;
-import java.io.StreamCorruptedException;
-import java.net.SocketTimeoutException;
-import java.nio.charset.Charset;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.NavigableMap;
-import java.util.Objects;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.apache.sshd.client.channel.ChannelSubsystem;
-import org.apache.sshd.client.channel.ClientChannel;
-import org.apache.sshd.client.session.ClientSession;
-import org.apache.sshd.common.PropertyResolverUtils;
-import org.apache.sshd.common.SshException;
-import org.apache.sshd.common.subsystem.sftp.SftpConstants;
-import org.apache.sshd.common.subsystem.sftp.extensions.ParserUtils;
-import org.apache.sshd.common.subsystem.sftp.extensions.VersionsParser.Versions;
-import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.ValidateUtils;
-import org.apache.sshd.common.util.buffer.Buffer;
-import org.apache.sshd.common.util.buffer.BufferUtils;
-import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
-
-/**
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class DefaultSftpClient extends AbstractSftpClient {
- private final ClientSession clientSession;
- private final ChannelSubsystem channel;
- private final Map<Integer, Buffer> messages = new HashMap<>();
- private final AtomicInteger cmdId = new AtomicInteger(100);
- private final Buffer receiveBuffer = new ByteArrayBuffer();
- private final byte[] workBuf = new byte[Integer.BYTES];
- private final AtomicInteger versionHolder = new AtomicInteger(0);
- private final AtomicBoolean closing = new AtomicBoolean(false);
- private final NavigableMap<String, byte[]> extensions = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
- private final NavigableMap<String, byte[]> exposedExtensions = Collections.unmodifiableNavigableMap(extensions);
- private Charset nameDecodingCharset = DEFAULT_NAME_DECODING_CHARSET;
-
- public DefaultSftpClient(ClientSession clientSession) throws IOException {
- this.nameDecodingCharset = PropertyResolverUtils.getCharset(clientSession, NAME_DECODING_CHARSET, DEFAULT_NAME_DECODING_CHARSET);
- this.clientSession = Objects.requireNonNull(clientSession, "No client session");
- this.channel = clientSession.createSubsystemChannel(SftpConstants.SFTP_SUBSYSTEM_NAME);
- this.channel.setOut(new OutputStream() {
- private final byte[] singleByte = new byte[1];
- @Override
- public void write(int b) throws IOException {
- synchronized (singleByte) {
- singleByte[0] = (byte) b;
- write(singleByte);
- }
- }
-
- @Override
- public void write(byte[] b, int off, int len) throws IOException {
- data(b, off, len);
- }
- });
- this.channel.setErr(new ByteArrayOutputStream(Byte.MAX_VALUE));
-
- long initializationTimeout = clientSession.getLongProperty(SFTP_CHANNEL_OPEN_TIMEOUT, DEFAULT_CHANNEL_OPEN_TIMEOUT);
- this.channel.open().verify(initializationTimeout);
- this.channel.onClose(() -> {
- synchronized (messages) {
- closing.set(true);
- messages.notifyAll();
- }
-
- if (versionHolder.get() <= 0) {
- log.warn("onClose({}) closed before version negotiated", channel);
- }
- });
-
- try {
- init(initializationTimeout);
- } catch (IOException | RuntimeException e) {
- this.channel.close(true);
- throw e;
- }
- }
-
- @Override
- public int getVersion() {
- return versionHolder.get();
- }
-
- @Override
- public ClientSession getClientSession() {
- return clientSession;
- }
-
- @Override
- public ClientChannel getClientChannel() {
- return channel;
- }
-
- @Override
- public NavigableMap<String, byte[]> getServerExtensions() {
- return exposedExtensions;
- }
-
- @Override
- public Charset getNameDecodingCharset() {
- return nameDecodingCharset;
- }
-
- @Override
- public void setNameDecodingCharset(Charset nameDecodingCharset) {
- this.nameDecodingCharset = Objects.requireNonNull(nameDecodingCharset, "No charset provided");
- }
-
- @Override
- public boolean isClosing() {
- return closing.get();
- }
-
- @Override
- public boolean isOpen() {
- return this.channel.isOpen();
- }
-
- @Override
- public void close() throws IOException {
- if (isOpen()) {
- this.channel.close(false);
- }
- }
-
- /**
- * Receive binary data
- * @param buf The buffer for the incoming data
- * @param start Offset in buffer to place the data
- * @param len Available space in buffer for the data
- * @return Actual size of received data
- * @throws IOException If failed to receive incoming data
- */
- protected int data(byte[] buf, int start, int len) throws IOException {
- Buffer incoming = new ByteArrayBuffer(buf, start, len);
- // If we already have partial data, we need to append it to the buffer and use it
- if (receiveBuffer.available() > 0) {
- receiveBuffer.putBuffer(incoming);
- incoming = receiveBuffer;
- }
-
- // Process commands
- int rpos = incoming.rpos();
- for (int count = 1; receive(incoming); count++) {
- if (log.isTraceEnabled()) {
- log.trace("data({}) Processed {} data messages", getClientChannel(), count);
- }
- }
-
- int read = incoming.rpos() - rpos;
- // Compact and add remaining data
- receiveBuffer.compact();
- if (receiveBuffer != incoming && incoming.available() > 0) {
- receiveBuffer.putBuffer(incoming);
- }
-
- return read;
- }
-
- /**
- * Read SFTP packets from buffer
- *
- * @param incoming The received {@link Buffer}
- * @return {@code true} if data from incoming buffer was processed
- * @throws IOException if failed to process the buffer
- * @see #process(Buffer)
- */
- protected boolean receive(Buffer incoming) throws IOException {
- int rpos = incoming.rpos();
- int wpos = incoming.wpos();
- ClientSession session = getClientSession();
- session.resetIdleTimeout();
-
- if ((wpos - rpos) > 4) {
- int length = incoming.getInt();
- if (length < 5) {
- throw new IOException("Illegal sftp packet length: " + length);
- }
- if ((wpos - rpos) >= (length + 4)) {
- incoming.rpos(rpos);
- incoming.wpos(rpos + 4 + length);
- process(incoming);
- incoming.rpos(rpos + 4 + length);
- incoming.wpos(wpos);
- return true;
- }
- }
- incoming.rpos(rpos);
- return false;
- }
-
- /**
- * Process an SFTP packet
- *
- * @param incoming The received {@link Buffer}
- * @throws IOException if failed to process the buffer
- */
- protected void process(Buffer incoming) throws IOException {
- // create a copy of the buffer in case it is being re-used
- Buffer buffer = new ByteArrayBuffer(incoming.available() + Long.SIZE, false);
- buffer.putBuffer(incoming);
-
- int rpos = buffer.rpos();
- int length = buffer.getInt();
- int type = buffer.getUByte();
- Integer id = buffer.getInt();
- buffer.rpos(rpos);
-
- if (log.isTraceEnabled()) {
- log.trace("process({}) id={}, type={}, len={}",
- getClientChannel(), id, SftpConstants.getCommandMessageName(type), length);
- }
-
- synchronized (messages) {
- messages.put(id, buffer);
- messages.notifyAll();
- }
- }
-
- @Override
- public int send(int cmd, Buffer buffer) throws IOException {
- int id = cmdId.incrementAndGet();
- int len = buffer.available();
- if (log.isTraceEnabled()) {
- log.trace("send({}) cmd={}, len={}, id={}",
- getClientChannel(), SftpConstants.getCommandMessageName(cmd), len, id);
- }
-
- OutputStream dos = channel.getInvertedIn();
- BufferUtils.writeInt(dos, 1 /* cmd */ + Integer.BYTES /* id */ + len, workBuf);
- dos.write(cmd & 0xFF);
- BufferUtils.writeInt(dos, id, workBuf);
- dos.write(buffer.array(), buffer.rpos(), len);
- dos.flush();
- return id;
- }
-
- @Override
- public Buffer receive(int id) throws IOException {
- Integer reqId = id;
- synchronized (messages) {
- for (int count = 1;; count++) {
- if (isClosing() || (!isOpen())) {
- throw new SshException("Channel is being closed");
- }
-
- Buffer buffer = messages.remove(reqId);
- if (buffer != null) {
- return buffer;
- }
-
- try {
- messages.wait();
- } catch (InterruptedException e) {
- throw (IOException) new InterruptedIOException("Interrupted while waiting for messages at iteration #" + count).initCause(e);
- }
- }
- }
- }
-
- protected Buffer read() throws IOException {
- InputStream dis = channel.getInvertedOut();
- int length = BufferUtils.readInt(dis, workBuf);
- // must have at least command + length
- if (length < (1 + Integer.BYTES)) {
- throw new IllegalArgumentException("Bad length: " + length);
- }
-
- Buffer buffer = new ByteArrayBuffer(length + Integer.BYTES, false);
- buffer.putInt(length);
- int nb = length;
- while (nb > 0) {
- int readLen = dis.read(buffer.array(), buffer.wpos(), nb);
- if (readLen < 0) {
- throw new IllegalArgumentException("Premature EOF while read " + length + " bytes - remaining=" + nb);
- }
- buffer.wpos(buffer.wpos() + readLen);
- nb -= readLen;
- }
-
- return buffer;
- }
-
- protected void init(long initializationTimeout) throws IOException {
- ValidateUtils.checkTrue(initializationTimeout > 0L, "Invalid initialization timeout: %d", initializationTimeout);
-
- // Send init packet
- OutputStream dos = channel.getInvertedIn();
- BufferUtils.writeInt(dos, 5 /* total length */, workBuf);
- dos.write(SftpConstants.SSH_FXP_INIT);
- BufferUtils.writeInt(dos, SftpConstants.SFTP_V6, workBuf);
- dos.flush();
-
- Buffer buffer;
- Integer reqId;
- synchronized (messages) {
- /*
- * We need to use a timeout since if the remote server does not support
- * SFTP, we will not know it immediately. This is due to the fact that the
- * request for the subsystem does not contain a reply as to its success or
- * failure. Thus, the SFTP channel is created by the client, but there is
- * no one on the other side to reply - thus the need for the timeout
- */
- for (long remainingTimeout = initializationTimeout; (remainingTimeout > 0L) && messages.isEmpty() && (!isClosing()) && isOpen();) {
- try {
- long sleepStart = System.nanoTime();
- messages.wait(remainingTimeout);
- long sleepEnd = System.nanoTime();
- long sleepDuration = sleepEnd - sleepStart;
- long sleepMillis = TimeUnit.NANOSECONDS.toMillis(sleepDuration);
- if (sleepMillis < 1L) {
- remainingTimeout--;
- } else {
- remainingTimeout -= sleepMillis;
- }
- } catch (InterruptedException e) {
- throw (IOException) new InterruptedIOException("Interrupted init()").initCause(e);
- }
- }
-
- if (isClosing() || (!isOpen())) {
- throw new EOFException("Closing while await init message");
- }
-
- if (messages.isEmpty()) {
- throw new SocketTimeoutException("No incoming initialization response received within " + initializationTimeout + " msec.");
- }
-
- Collection<Integer> ids = messages.keySet();
- Iterator<Integer> iter = ids.iterator();
- reqId = iter.next();
- buffer = messages.remove(reqId);
- }
-
- int length = buffer.getInt();
- int type = buffer.getUByte();
- int id = buffer.getInt();
- if (log.isTraceEnabled()) {
- log.trace("init({}) id={} type={} len={}",
- getClientChannel(), id, SftpConstants.getCommandMessageName(type), length);
- }
-
- if (type == SftpConstants.SSH_FXP_VERSION) {
- if (id < SftpConstants.SFTP_V3) {
- throw new SshException("Unsupported sftp version " + id);
- }
- versionHolder.set(id);
-
- if (log.isTraceEnabled()) {
- log.trace("init({}) version={}", getClientChannel(), versionHolder);
- }
-
- while (buffer.available() > 0) {
- String name = buffer.getString();
- byte[] data = buffer.getBytes();
- if (log.isTraceEnabled()) {
- log.trace("init({}) added extension=", getClientChannel(), name);
- }
- extensions.put(name, data);
- }
- } else if (type == SftpConstants.SSH_FXP_STATUS) {
- int substatus = buffer.getInt();
- String msg = buffer.getString();
- String lang = buffer.getString();
- if (log.isTraceEnabled()) {
- log.trace("init({})[id={}] - status: {} [{}] {}",
- getClientChannel(), id, SftpConstants.getStatusName(substatus), lang, msg);
- }
-
- throwStatusException(SftpConstants.SSH_FXP_INIT, id, substatus, msg, lang);
- } else {
- handleUnexpectedPacket(SftpConstants.SSH_FXP_INIT, SftpConstants.SSH_FXP_VERSION, id, type, length, buffer);
- }
- }
-
- /**
- * @param selector The {@link SftpVersionSelector} to use - ignored if {@code null}
- * @return The selected version (may be same as current)
- * @throws IOException If failed to negotiate
- */
- public int negotiateVersion(SftpVersionSelector selector) throws IOException {
- int current = getVersion();
- if (selector == null) {
- return current;
- }
-
- Set<Integer> available = GenericUtils.asSortedSet(Collections.singleton(current));
- Map<String, ?> parsed = getParsedServerExtensions();
- Collection<String> extensions = ParserUtils.supportedExtensions(parsed);
- if ((GenericUtils.size(extensions) > 0) && extensions.contains(SftpConstants.EXT_VERSION_SELECT)) {
- Versions vers = GenericUtils.isEmpty(parsed) ? null : (Versions) parsed.get(SftpConstants.EXT_VERSIONS);
- Collection<String> reported = (vers == null) ? null : vers.getVersions();
- if (GenericUtils.size(reported) > 0) {
- for (String v : reported) {
- if (!available.add(Integer.valueOf(v))) {
- continue; // debug breakpoint
- }
- }
- }
- }
-
- int selected = selector.selectVersion(getClientSession(), current, new ArrayList<>(available));
- if (log.isDebugEnabled()) {
- log.debug("negotiateVersion({}) current={} {} -> {}", getClientChannel(), current, available, selected);
- }
-
- if (selected == current) {
- return current;
- }
-
- if (!available.contains(selected)) {
- throw new StreamCorruptedException("Selected version (" + selected + ") not part of available: " + available);
- }
-
- String verVal = String.valueOf(selected);
- Buffer buffer = new ByteArrayBuffer(Integer.BYTES + SftpConstants.EXT_VERSION_SELECT.length() // extension name
- + Integer.BYTES + verVal.length() + Byte.SIZE, false);
- buffer.putString(SftpConstants.EXT_VERSION_SELECT);
- buffer.putString(verVal);
- checkCommandStatus(SftpConstants.SSH_FXP_EXTENDED, buffer);
- versionHolder.set(selected);
- return selected;
- }
-}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpAclFileAttributeView.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpAclFileAttributeView.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpAclFileAttributeView.java
index f3ca4f3..7cada6e 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpAclFileAttributeView.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpAclFileAttributeView.java
@@ -28,6 +28,8 @@ import java.nio.file.attribute.PosixFileAttributes;
import java.nio.file.attribute.UserPrincipal;
import java.util.List;
+import org.apache.sshd.client.subsystem.sftp.impl.AbstractSftpFileAttributeView;
+
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java
index c835e35..c860cb4 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java
@@ -496,7 +496,7 @@ public interface SftpClient extends SubsystemClient {
private final String longFilename;
private final Attributes attributes;
- DirEntry(String filename, String longFilename, Attributes attributes) {
+ public DirEntry(String filename, String longFilename, Attributes attributes) {
this.filename = filename;
this.longFilename = longFilename;
this.attributes = attributes;
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClientCreator.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClientCreator.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClientCreator.java
index 8e41729..a282b87 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClientCreator.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClientCreator.java
@@ -61,15 +61,25 @@ public interface SftpClientCreator {
*/
SftpClient createSftpClient(SftpVersionSelector selector) throws IOException;
- FileSystem createSftpFileSystem() throws IOException;
+ default FileSystem createSftpFileSystem() throws IOException {
+ return createSftpFileSystem(SftpVersionSelector.CURRENT);
+ }
- FileSystem createSftpFileSystem(int version) throws IOException;
+ default FileSystem createSftpFileSystem(int version) throws IOException {
+ return createSftpFileSystem(SftpVersionSelector.fixedVersionSelector(version));
+ }
- FileSystem createSftpFileSystem(SftpVersionSelector selector) throws IOException;
+ default FileSystem createSftpFileSystem(SftpVersionSelector selector) throws IOException {
+ return createSftpFileSystem(selector, SftpClient.DEFAULT_READ_BUFFER_SIZE, SftpClient.DEFAULT_WRITE_BUFFER_SIZE);
+ }
- FileSystem createSftpFileSystem(int readBufferSize, int writeBufferSize) throws IOException;
+ default FileSystem createSftpFileSystem(int version, int readBufferSize, int writeBufferSize) throws IOException {
+ return createSftpFileSystem(SftpVersionSelector.fixedVersionSelector(version), readBufferSize, writeBufferSize);
+ }
- FileSystem createSftpFileSystem(int version, int readBufferSize, int writeBufferSize) throws IOException;
+ default FileSystem createSftpFileSystem(int readBufferSize, int writeBufferSize) throws IOException {
+ return createSftpFileSystem(SftpVersionSelector.CURRENT, readBufferSize, writeBufferSize);
+ }
FileSystem createSftpFileSystem(SftpVersionSelector selector, int readBufferSize, int writeBufferSize) throws IOException;
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClientFactory.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClientFactory.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClientFactory.java
new file mode 100644
index 0000000..15e321f
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClientFactory.java
@@ -0,0 +1,51 @@
+/*
+ * 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.sshd.client.subsystem.sftp;
+
+import java.io.IOException;
+import java.nio.file.FileSystem;
+
+import org.apache.sshd.client.session.ClientSession;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface SftpClientFactory {
+ /**
+ * @param session The {@link ClientSession} to which the SFTP client should be attached
+ * @param selector The {@link SftpVersionSelector} to use in order to negotiate the SFTP version
+ * @return The created {@link SftpClient} instance
+ * @throws IOException If failed to create the client
+ */
+ SftpClient createSftpClient(ClientSession session, SftpVersionSelector selector) throws IOException;
+
+ /**
+ * @param session The {@link ClientSession} to which the SFTP client backing the file system should be attached
+ * @param selector The {@link SftpVersionSelector} to use in order to negotiate the SFTP version
+ * @param readBufferSize Default I/O read buffer size
+ * @param writeBufferSize Default I/O write buffer size
+ * @return The created {@link FileSystem} instance
+ * @throws IOException If failed to create the instance
+ */
+ FileSystem createSftpFileSystem(
+ ClientSession session, SftpVersionSelector selector, int readBufferSize, int writeBufferSize)
+ throws IOException;
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClientFactoryManager.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClientFactoryManager.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClientFactoryManager.java
new file mode 100644
index 0000000..02bc5f6
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClientFactoryManager.java
@@ -0,0 +1,37 @@
+/*
+ * 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.sshd.client.subsystem.sftp;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface SftpClientFactoryManager {
+ /**
+ * @return The (never {@code null}) {@link SftpClientFactory} instance
+ */
+ SftpClientFactory getSftpClientFactory();
+
+ /**
+ * @param sftpClientFactory The {@link SftpClientFactory} instance to use - if {@code null}
+ * then an internal default will be used
+ */
+ void setSftpClientFactory(SftpClientFactory sftpClientFactory);
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystem.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystem.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystem.java
index f028a5b..1ca0283 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystem.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystem.java
@@ -42,6 +42,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import org.apache.sshd.client.channel.ClientChannel;
import org.apache.sshd.client.session.ClientSession;
import org.apache.sshd.client.session.ClientSessionHolder;
+import org.apache.sshd.client.subsystem.sftp.impl.AbstractSftpClient;
import org.apache.sshd.common.file.util.BaseFileSystem;
import org.apache.sshd.common.subsystem.sftp.SftpConstants;
import org.apache.sshd.common.util.GenericUtils;
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
index 5b2f49d..116282a 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
@@ -831,7 +831,7 @@ public class SftpFileSystemProvider extends FileSystemProvider {
return map;
}
- protected SftpClient.Attributes readRemoteAttributes(SftpPath path, LinkOption... options) throws IOException {
+ public SftpClient.Attributes readRemoteAttributes(SftpPath path, LinkOption... options) throws IOException {
SftpFileSystem fs = path.getFileSystem();
try (SftpClient client = fs.getClient()) {
try {
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPosixFileAttributeView.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPosixFileAttributeView.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPosixFileAttributeView.java
index 13d2119..1fb614c 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPosixFileAttributeView.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPosixFileAttributeView.java
@@ -29,6 +29,7 @@ import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.UserPrincipal;
import java.util.Set;
+import org.apache.sshd.client.subsystem.sftp.impl.AbstractSftpFileAttributeView;
import org.apache.sshd.common.util.GenericUtils;
/**
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/AbstractSftpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/AbstractSftpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/AbstractSftpClient.java
new file mode 100644
index 0000000..a1d2014
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/AbstractSftpClient.java
@@ -0,0 +1,1134 @@
+/*
+ * 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.sshd.client.subsystem.sftp.impl;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.attribute.FileTime;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.sshd.client.channel.ClientChannel;
+import org.apache.sshd.client.subsystem.AbstractSubsystemClient;
+import org.apache.sshd.client.subsystem.sftp.RawSftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.client.subsystem.sftp.extensions.BuiltinSftpClientExtensions;
+import org.apache.sshd.client.subsystem.sftp.extensions.SftpClientExtension;
+import org.apache.sshd.client.subsystem.sftp.extensions.SftpClientExtensionFactory;
+import org.apache.sshd.common.SshException;
+import org.apache.sshd.common.channel.Channel;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.subsystem.sftp.SftpException;
+import org.apache.sshd.common.subsystem.sftp.SftpHelper;
+import org.apache.sshd.common.subsystem.sftp.SftpUniversalOwnerAndGroup;
+import org.apache.sshd.common.subsystem.sftp.extensions.ParserUtils;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
+
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class AbstractSftpClient extends AbstractSubsystemClient implements SftpClient, RawSftpClient {
+ private final Attributes fileOpenAttributes = new Attributes();
+ private final AtomicReference<Map<String, Object>> parsedExtensionsHolder = new AtomicReference<>(null);
+
+ protected AbstractSftpClient() {
+ fileOpenAttributes.setType(SftpConstants.SSH_FILEXFER_TYPE_REGULAR);
+ }
+
+ @Override
+ public Channel getChannel() {
+ return getClientChannel();
+ }
+
+ @Override
+ public <E extends SftpClientExtension> E getExtension(Class<? extends E> extensionType) {
+ Object instance = getExtension(BuiltinSftpClientExtensions.fromType(extensionType));
+ if (instance == null) {
+ return null;
+ } else {
+ return extensionType.cast(instance);
+ }
+ }
+
+ @Override
+ public SftpClientExtension getExtension(String extensionName) {
+ return getExtension(BuiltinSftpClientExtensions.fromName(extensionName));
+ }
+
+ protected SftpClientExtension getExtension(SftpClientExtensionFactory factory) {
+ if (factory == null) {
+ return null;
+ }
+
+ Map<String, byte[]> extensions = getServerExtensions();
+ Map<String, Object> parsed = getParsedServerExtensions(extensions);
+ return factory.create(this, this, extensions, parsed);
+ }
+
+ protected Map<String, Object> getParsedServerExtensions() {
+ return getParsedServerExtensions(getServerExtensions());
+ }
+
+ protected Map<String, Object> getParsedServerExtensions(Map<String, byte[]> extensions) {
+ Map<String, Object> parsed = parsedExtensionsHolder.get();
+ if (parsed == null) {
+ parsed = ParserUtils.parse(extensions);
+ if (parsed == null) {
+ parsed = Collections.emptyMap();
+ }
+ parsedExtensionsHolder.set(parsed);
+ }
+
+ return parsed;
+ }
+
+ /**
+ * @param cmd The command that was sent whose response contains the name to be decoded
+ * @param buf The {@link Buffer} containing the encoded name
+ * @return The decoded referenced name
+ */
+ protected String getReferencedName(int cmd, Buffer buf) {
+ Charset cs = getNameDecodingCharset();
+ return buf.getString(cs);
+ }
+
+ /**
+ * Sends the specified command, waits for the response and then invokes {@link #checkResponseStatus(int, Buffer)}
+ * @param cmd The command to send
+ * @param request The request {@link Buffer}
+ * @throws IOException If failed to send, receive or check the returned status
+ * @see #send(int, Buffer)
+ * @see #receive(int)
+ * @see #checkResponseStatus(int, Buffer)
+ */
+ protected void checkCommandStatus(int cmd, Buffer request) throws IOException {
+ int reqId = send(cmd, request);
+ Buffer response = receive(reqId);
+ checkResponseStatus(cmd, response);
+ }
+
+ /**
+ * Checks if the incoming response is an {@code SSH_FXP_STATUS} one,
+ * and if so whether the substatus is {@code SSH_FX_OK}.
+ *
+ * @param cmd The sent command opcode
+ * @param buffer The received response {@link Buffer}
+ * @throws IOException If response does not carry a status or carries
+ * a bad status code
+ * @see #checkResponseStatus(int, int, int, String, String)
+ */
+ protected void checkResponseStatus(int cmd, Buffer buffer) throws IOException {
+ int length = buffer.getInt();
+ int type = buffer.getUByte();
+ int id = buffer.getInt();
+ if (type == SftpConstants.SSH_FXP_STATUS) {
+ int substatus = buffer.getInt();
+ String msg = buffer.getString();
+ String lang = buffer.getString();
+ checkResponseStatus(cmd, id, substatus, msg, lang);
+ } else {
+ //noinspection ThrowableResultOfMethodCallIgnored
+ handleUnexpectedPacket(cmd, SftpConstants.SSH_FXP_STATUS, id, type, length, buffer);
+ }
+ }
+
+ /**
+ * @param cmd The sent command opcode
+ * @param id The request id
+ * @param substatus The sub-status value
+ * @param msg The message
+ * @param lang The language
+ * @throws IOException if the sub-status is not {@code SSH_FX_OK}
+ * @see #throwStatusException(int, int, int, String, String)
+ */
+ protected void checkResponseStatus(int cmd, int id, int substatus, String msg, String lang) throws IOException {
+ if (log.isTraceEnabled()) {
+ log.trace("checkResponseStatus({})[id={}] cmd={} status={} lang={} msg={}",
+ getClientChannel(), id, SftpConstants.getCommandMessageName(cmd),
+ SftpConstants.getStatusName(substatus), lang, msg);
+ }
+
+ if (substatus != SftpConstants.SSH_FX_OK) {
+ throwStatusException(cmd, id, substatus, msg, lang);
+ }
+ }
+
+ protected void throwStatusException(int cmd, int id, int substatus, String msg, String lang) throws IOException {
+ throw new SftpException(substatus, msg);
+ }
+
+ /**
+ * @param cmd Command to be sent
+ * @param request The {@link Buffer} containing the request
+ * @return The received handle identifier
+ * @throws IOException If failed to send/receive or process the response
+ * @see #send(int, Buffer)
+ * @see #receive(int)
+ * @see #checkHandleResponse(int, Buffer)
+ */
+ protected byte[] checkHandle(int cmd, Buffer request) throws IOException {
+ int reqId = send(cmd, request);
+ Buffer response = receive(reqId);
+ return checkHandleResponse(cmd, response);
+ }
+
+ protected byte[] checkHandleResponse(int cmd, Buffer buffer) throws IOException {
+ int length = buffer.getInt();
+ int type = buffer.getUByte();
+ int id = buffer.getInt();
+ if (type == SftpConstants.SSH_FXP_HANDLE) {
+ return ValidateUtils.checkNotNullAndNotEmpty(buffer.getBytes(), "Null/empty handle in buffer", GenericUtils.EMPTY_OBJECT_ARRAY);
+ }
+
+ if (type == SftpConstants.SSH_FXP_STATUS) {
+ int substatus = buffer.getInt();
+ String msg = buffer.getString();
+ String lang = buffer.getString();
+ if (log.isTraceEnabled()) {
+ log.trace("checkHandleResponse({})[id={}] {} - status: {} [{}] {}",
+ getClientChannel(), id, SftpConstants.getCommandMessageName(cmd),
+ SftpConstants.getStatusName(substatus), lang, msg);
+ }
+ throwStatusException(cmd, id, substatus, msg, lang);
+ }
+
+ return handleUnexpectedHandlePacket(cmd, id, type, length, buffer);
+ }
+
+ protected byte[] handleUnexpectedHandlePacket(int cmd, int id, int type, int length, Buffer buffer) throws IOException {
+ handleUnexpectedPacket(cmd, SftpConstants.SSH_FXP_HANDLE, id, type, length, buffer);
+ throw new SshException("No handling for unexpected handle packet id=" + id
+ + ", type=" + SftpConstants.getCommandMessageName(type) + ", length=" + length);
+ }
+
+ /**
+ * @param cmd Command to be sent
+ * @param request Request {@link Buffer}
+ * @return The decoded response {@code Attributes}
+ * @throws IOException If failed to send/receive or process the response
+ * @see #send(int, Buffer)
+ * @see #receive(int)
+ * @see #checkAttributesResponse(int, Buffer)
+ */
+ protected Attributes checkAttributes(int cmd, Buffer request) throws IOException {
+ int reqId = send(cmd, request);
+ Buffer response = receive(reqId);
+ return checkAttributesResponse(cmd, response);
+ }
+
+ protected Attributes checkAttributesResponse(int cmd, Buffer buffer) throws IOException {
+ int length = buffer.getInt();
+ int type = buffer.getUByte();
+ int id = buffer.getInt();
+ if (type == SftpConstants.SSH_FXP_ATTRS) {
+ return readAttributes(cmd, buffer);
+ }
+
+ if (type == SftpConstants.SSH_FXP_STATUS) {
+ int substatus = buffer.getInt();
+ String msg = buffer.getString();
+ String lang = buffer.getString();
+ if (log.isTraceEnabled()) {
+ log.trace("checkAttributesResponse()[id={}] {} - status: {} [{}] {}",
+ getClientChannel(), id, SftpConstants.getCommandMessageName(cmd),
+ SftpConstants.getStatusName(substatus), lang, msg);
+ }
+ throwStatusException(cmd, id, substatus, msg, lang);
+ }
+
+ return handleUnexpectedAttributesPacket(cmd, id, type, length, buffer);
+ }
+
+ protected Attributes handleUnexpectedAttributesPacket(int cmd, int id, int type, int length, Buffer buffer) throws IOException {
+ IOException err = handleUnexpectedPacket(cmd, SftpConstants.SSH_FXP_ATTRS, id, type, length, buffer);
+ if (err != null) {
+ throw err;
+ }
+
+ return null;
+ }
+
+ /**
+ * @param cmd Command to be sent
+ * @param request The request {@link Buffer}
+ * @return The retrieved name
+ * @throws IOException If failed to send/receive or process the response
+ * @see #send(int, Buffer)
+ * @see #receive(int)
+ * @see #checkOneNameResponse(int, Buffer)
+ */
+ protected String checkOneName(int cmd, Buffer request) throws IOException {
+ int reqId = send(cmd, request);
+ Buffer response = receive(reqId);
+ return checkOneNameResponse(cmd, response);
+ }
+
+ protected String checkOneNameResponse(int cmd, Buffer buffer) throws IOException {
+ int length = buffer.getInt();
+ int type = buffer.getUByte();
+ int id = buffer.getInt();
+ if (type == SftpConstants.SSH_FXP_NAME) {
+ int len = buffer.getInt();
+ if (len != 1) {
+ throw new SshException("SFTP error: received " + len + " names instead of 1");
+ }
+ String name = getReferencedName(cmd, buffer);
+ String longName = null;
+ int version = getVersion();
+ if (version == SftpConstants.SFTP_V3) {
+ longName = getReferencedName(cmd, buffer);
+ }
+
+ Attributes attrs = readAttributes(cmd, buffer);
+ Boolean indicator = SftpHelper.getEndOfListIndicatorValue(buffer, version);
+ // TODO decide what to do if not-null and not TRUE
+ if (log.isTraceEnabled()) {
+ log.trace("checkOneNameResponse({})[id={}] {} ({})[{}] eol={}: {}",
+ getClientChannel(), id, SftpConstants.getCommandMessageName(cmd),
+ name, longName, indicator, attrs);
+ }
+ return name;
+ }
+
+ if (type == SftpConstants.SSH_FXP_STATUS) {
+ int substatus = buffer.getInt();
+ String msg = buffer.getString();
+ String lang = buffer.getString();
+ if (log.isTraceEnabled()) {
+ log.trace("checkOneNameResponse({})[id={}] {} status: {} [{}] {}",
+ getClientChannel(), id, SftpConstants.getCommandMessageName(cmd),
+ SftpConstants.getStatusName(substatus), lang, msg);
+ }
+
+ throwStatusException(cmd, id, substatus, msg, lang);
+ }
+
+ return handleUnknownOneNamePacket(cmd, id, type, length, buffer);
+ }
+
+ protected String handleUnknownOneNamePacket(int cmd, int id, int type, int length, Buffer buffer) throws IOException {
+ IOException err = handleUnexpectedPacket(cmd, SftpConstants.SSH_FXP_NAME, id, type, length, buffer);
+ if (err != null) {
+ throw err;
+ }
+
+ return null;
+ }
+
+ protected Attributes readAttributes(int cmd, Buffer buffer) throws IOException {
+ Attributes attrs = new Attributes();
+ int flags = buffer.getInt();
+ int version = getVersion();
+ if (version == SftpConstants.SFTP_V3) {
+ if ((flags & SftpConstants.SSH_FILEXFER_ATTR_SIZE) != 0) {
+ attrs.setSize(buffer.getLong());
+ }
+ if ((flags & SftpConstants.SSH_FILEXFER_ATTR_UIDGID) != 0) {
+ attrs.owner(buffer.getInt(), buffer.getInt());
+ }
+ if ((flags & SftpConstants.SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
+ int perms = buffer.getInt();
+ attrs.setPermissions(perms);
+ attrs.setType(SftpHelper.permissionsToFileType(perms));
+ }
+
+ if ((flags & SftpConstants.SSH_FILEXFER_ATTR_ACMODTIME) != 0) {
+ attrs.setAccessTime(SftpHelper.readTime(buffer, version, flags));
+ attrs.setModifyTime(SftpHelper.readTime(buffer, version, flags));
+ }
+ } else if (version >= SftpConstants.SFTP_V4) {
+ attrs.setType(buffer.getUByte());
+ if ((flags & SftpConstants.SSH_FILEXFER_ATTR_SIZE) != 0) {
+ attrs.setSize(buffer.getLong());
+ }
+
+ if ((version >= SftpConstants.SFTP_V6) && ((flags & SftpConstants.SSH_FILEXFER_ATTR_ALLOCATION_SIZE) != 0)) {
+ @SuppressWarnings("unused")
+ long allocSize = buffer.getLong(); // TODO handle allocation size
+ }
+
+ if ((flags & SftpConstants.SSH_FILEXFER_ATTR_OWNERGROUP) != 0) {
+ attrs.setOwner(buffer.getString());
+ attrs.setGroup(buffer.getString());
+ }
+ if ((flags & SftpConstants.SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
+ attrs.setPermissions(buffer.getInt());
+ }
+
+ // update the permissions according to the type
+ int perms = attrs.getPermissions();
+ perms |= SftpHelper.fileTypeToPermission(attrs.getType());
+ attrs.setPermissions(perms);
+
+ if ((flags & SftpConstants.SSH_FILEXFER_ATTR_ACCESSTIME) != 0) {
+ attrs.setAccessTime(SftpHelper.readTime(buffer, version, flags));
+ }
+ if ((flags & SftpConstants.SSH_FILEXFER_ATTR_CREATETIME) != 0) {
+ attrs.setCreateTime(SftpHelper.readTime(buffer, version, flags));
+ }
+ if ((flags & SftpConstants.SSH_FILEXFER_ATTR_MODIFYTIME) != 0) {
+ attrs.setModifyTime(SftpHelper.readTime(buffer, version, flags));
+ }
+ if ((version >= SftpConstants.SFTP_V6) && (flags & SftpConstants.SSH_FILEXFER_ATTR_CTIME) != 0) {
+ @SuppressWarnings("unused")
+ FileTime attrsChangedTime = SftpHelper.readTime(buffer, version, flags); // TODO the last time the file attributes were changed
+ }
+
+ if ((flags & SftpConstants.SSH_FILEXFER_ATTR_ACL) != 0) {
+ attrs.setAcl(SftpHelper.readACLs(buffer, version));
+ }
+
+ if ((flags & SftpConstants.SSH_FILEXFER_ATTR_BITS) != 0) {
+ @SuppressWarnings("unused")
+ int bits = buffer.getInt();
+ @SuppressWarnings("unused")
+ int valid = 0xffffffff;
+ if (version >= SftpConstants.SFTP_V6) {
+ valid = buffer.getInt();
+ }
+ // TODO: handle attrib bits
+ }
+
+ if (version >= SftpConstants.SFTP_V6) {
+ if ((flags & SftpConstants.SSH_FILEXFER_ATTR_TEXT_HINT) != 0) {
+ @SuppressWarnings("unused")
+ boolean text = buffer.getBoolean(); // TODO: handle text
+ }
+ if ((flags & SftpConstants.SSH_FILEXFER_ATTR_MIME_TYPE) != 0) {
+ @SuppressWarnings("unused")
+ String mimeType = buffer.getString(); // TODO: handle mime-type
+ }
+ if ((flags & SftpConstants.SSH_FILEXFER_ATTR_LINK_COUNT) != 0) {
+ @SuppressWarnings("unused")
+ int nlink = buffer.getInt(); // TODO: handle link-count
+ }
+ if ((flags & SftpConstants.SSH_FILEXFER_ATTR_UNTRANSLATED_NAME) != 0) {
+ @SuppressWarnings("unused")
+ String untranslated = getReferencedName(cmd, buffer); // TODO: handle untranslated-name
+ }
+ }
+ } else {
+ throw new IllegalStateException("readAttributes - unsupported version: " + version);
+ }
+
+ if ((flags & SftpConstants.SSH_FILEXFER_ATTR_EXTENDED) != 0) {
+ attrs.setExtensions(SftpHelper.readExtensions(buffer));
+ }
+
+ return attrs;
+ }
+
+ protected void writeAttributes(Buffer buffer, Attributes attributes) throws IOException {
+ int version = getVersion();
+ int flagsMask = 0;
+ Collection<Attribute> flags = Objects.requireNonNull(attributes, "No attributes").getFlags();
+ if (version == SftpConstants.SFTP_V3) {
+ for (Attribute a : flags) {
+ switch (a) {
+ case Size:
+ flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_SIZE;
+ break;
+ case UidGid:
+ flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_UIDGID;
+ break;
+ case Perms:
+ flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_PERMISSIONS;
+ break;
+ case AccessTime:
+ if (flags.contains(Attribute.ModifyTime)) {
+ flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_ACMODTIME;
+ }
+ break;
+ case ModifyTime:
+ if (flags.contains(Attribute.AccessTime)) {
+ flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_ACMODTIME;
+ }
+ break;
+ case Extensions:
+ flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_EXTENDED;
+ break;
+ default: // do nothing
+ }
+ }
+ buffer.putInt(flagsMask);
+ if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_SIZE) != 0) {
+ buffer.putLong(attributes.getSize());
+ }
+ if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_UIDGID) != 0) {
+ buffer.putInt(attributes.getUserId());
+ buffer.putInt(attributes.getGroupId());
+ }
+ if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
+ buffer.putInt(attributes.getPermissions());
+ }
+
+ if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_ACMODTIME) != 0) {
+ SftpHelper.writeTime(buffer, version, flagsMask, attributes.getAccessTime());
+ SftpHelper.writeTime(buffer, version, flagsMask, attributes.getModifyTime());
+ }
+ } else if (version >= SftpConstants.SFTP_V4) {
+ for (Attribute a : flags) {
+ switch (a) {
+ case Size:
+ flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_SIZE;
+ break;
+ case OwnerGroup:
+ flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_OWNERGROUP;
+ break;
+ case Perms:
+ flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_PERMISSIONS;
+ break;
+ case AccessTime:
+ flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_ACCESSTIME;
+ break;
+ case ModifyTime:
+ flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_MODIFYTIME;
+ break;
+ case CreateTime:
+ flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_CREATETIME;
+ break;
+ case Acl:
+ flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_ACL;
+ break;
+ case Extensions:
+ flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_EXTENDED;
+ break;
+ default: // do nothing
+ }
+ }
+ buffer.putInt(flagsMask);
+ buffer.putByte((byte) attributes.getType());
+ if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_SIZE) != 0) {
+ buffer.putLong(attributes.getSize());
+ }
+ if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_OWNERGROUP) != 0) {
+ String owner = attributes.getOwner();
+ buffer.putString(GenericUtils.isEmpty(owner) ? SftpUniversalOwnerAndGroup.Owner.getName() : owner);
+
+ String group = attributes.getGroup();
+ buffer.putString(GenericUtils.isEmpty(group) ? SftpUniversalOwnerAndGroup.Group.getName() : group);
+ }
+ if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
+ buffer.putInt(attributes.getPermissions());
+ }
+ if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_ACCESSTIME) != 0) {
+ SftpHelper.writeTime(buffer, version, flagsMask, attributes.getAccessTime());
+ }
+ if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_CREATETIME) != 0) {
+ SftpHelper.writeTime(buffer, version, flagsMask, attributes.getCreateTime());
+ }
+ if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_MODIFYTIME) != 0) {
+ SftpHelper.writeTime(buffer, version, flagsMask, attributes.getModifyTime());
+ }
+ if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_ACL) != 0) {
+ SftpHelper.writeACLs(buffer, version, attributes.getAcl());
+ }
+
+ // TODO: for v6+ add CTIME (see https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#page-21)
+ } else {
+ throw new UnsupportedOperationException("writeAttributes(" + attributes + ") unsupported version: " + version);
+ }
+
+ if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_EXTENDED) != 0) {
+ SftpHelper.writeExtensions(buffer, attributes.getExtensions());
+ }
+ }
+
+ @Override
+ public CloseableHandle open(String path, Collection<OpenMode> options) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("open(" + path + ")[" + options + "] client is closed");
+ }
+
+ /*
+ * Be consistent with FileChannel#open - if no mode specified then READ is assumed
+ */
+ if (GenericUtils.isEmpty(options)) {
+ options = EnumSet.of(OpenMode.Read);
+ }
+
+ Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */, false);
+ buffer.putString(path);
+ int version = getVersion();
+ int mode = 0;
+ if (version < SftpConstants.SFTP_V5) {
+ for (OpenMode m : options) {
+ switch (m) {
+ case Read:
+ mode |= SftpConstants.SSH_FXF_READ;
+ break;
+ case Write:
+ mode |= SftpConstants.SSH_FXF_WRITE;
+ break;
+ case Append:
+ mode |= SftpConstants.SSH_FXF_APPEND;
+ break;
+ case Create:
+ mode |= SftpConstants.SSH_FXF_CREAT;
+ break;
+ case Truncate:
+ mode |= SftpConstants.SSH_FXF_TRUNC;
+ break;
+ case Exclusive:
+ mode |= SftpConstants.SSH_FXF_EXCL;
+ break;
+ default: // do nothing
+ }
+ }
+ } else {
+ int access = 0;
+ if (options.contains(OpenMode.Read)) {
+ access |= SftpConstants.ACE4_READ_DATA | SftpConstants.ACE4_READ_ATTRIBUTES;
+ }
+ if (options.contains(OpenMode.Write)) {
+ access |= SftpConstants.ACE4_WRITE_DATA | SftpConstants.ACE4_WRITE_ATTRIBUTES;
+ }
+ if (options.contains(OpenMode.Append)) {
+ access |= SftpConstants.ACE4_APPEND_DATA;
+ }
+ buffer.putInt(access);
+
+ if (options.contains(OpenMode.Create) && options.contains(OpenMode.Exclusive)) {
+ mode |= SftpConstants.SSH_FXF_CREATE_NEW;
+ } else if (options.contains(OpenMode.Create) && options.contains(OpenMode.Truncate)) {
+ mode |= SftpConstants.SSH_FXF_CREATE_TRUNCATE;
+ } else if (options.contains(OpenMode.Create)) {
+ mode |= SftpConstants.SSH_FXF_OPEN_OR_CREATE;
+ } else if (options.contains(OpenMode.Truncate)) {
+ mode |= SftpConstants.SSH_FXF_TRUNCATE_EXISTING;
+ } else {
+ mode |= SftpConstants.SSH_FXF_OPEN_EXISTING;
+ }
+ }
+ buffer.putInt(mode);
+ writeAttributes(buffer, fileOpenAttributes);
+
+ CloseableHandle handle = new DefaultCloseableHandle(this, path, checkHandle(SftpConstants.SSH_FXP_OPEN, buffer));
+ if (log.isTraceEnabled()) {
+ log.trace("open({})[{}] options={}: {}", getClientSession(), path, options, handle);
+ }
+ return handle;
+ }
+
+ @Override
+ public void close(Handle handle) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("close(" + handle + ") client is closed");
+ }
+
+ if (log.isTraceEnabled()) {
+ log.trace("close({}) {}", getClientSession(), handle);
+ }
+
+ byte[] id = Objects.requireNonNull(handle, "No handle").getIdentifier();
+ Buffer buffer = new ByteArrayBuffer(id.length + Long.SIZE /* some extra fields */, false);
+ buffer.putBytes(id);
+ checkCommandStatus(SftpConstants.SSH_FXP_CLOSE, buffer);
+ }
+
+ @Override
+ public void remove(String path) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("remove(" + path + ") client is closed");
+ }
+
+ if (log.isDebugEnabled()) {
+ log.debug("remove({}) {}", getClientSession(), path);
+ }
+
+ Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */, false);
+ buffer.putString(path);
+ checkCommandStatus(SftpConstants.SSH_FXP_REMOVE, buffer);
+ }
+
+ @Override
+ public void rename(String oldPath, String newPath, Collection<CopyMode> options) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("rename(" + oldPath + " => " + newPath + ")[" + options + "] client is closed");
+ }
+
+ if (log.isDebugEnabled()) {
+ log.debug("rename({}) {} => {}", getClientSession(), oldPath, newPath);
+ }
+
+ Buffer buffer = new ByteArrayBuffer(oldPath.length() + newPath.length() + Long.SIZE /* some extra fields */, false);
+ buffer.putString(oldPath);
+ buffer.putString(newPath);
+
+ int numOptions = GenericUtils.size(options);
+ int version = getVersion();
+ if (version >= SftpConstants.SFTP_V5) {
+ int opts = 0;
+ if (numOptions > 0) {
+ for (CopyMode opt : options) {
+ switch (opt) {
+ case Atomic:
+ opts |= SftpConstants.SSH_FXP_RENAME_ATOMIC;
+ break;
+ case Overwrite:
+ opts |= SftpConstants.SSH_FXP_RENAME_OVERWRITE;
+ break;
+ default: // do nothing
+ }
+ }
+ }
+ buffer.putInt(opts);
+ } else if (numOptions > 0) {
+ throw new UnsupportedOperationException("rename(" + oldPath + " => " + newPath + ")"
+ + " - copy options can not be used with this SFTP version: " + options);
+ }
+ checkCommandStatus(SftpConstants.SSH_FXP_RENAME, buffer);
+ }
+
+ @Override
+ public int read(Handle handle, long fileOffset, byte[] dst, int dstOffset, int len, AtomicReference<Boolean> eofSignalled) throws IOException {
+ if (eofSignalled != null) {
+ eofSignalled.set(null);
+ }
+
+ if (!isOpen()) {
+ throw new IOException("read(" + handle + "/" + fileOffset + ")[" + dstOffset + "/" + len + "] client is closed");
+ }
+
+ byte[] id = Objects.requireNonNull(handle, "No handle").getIdentifier();
+ Buffer buffer = new ByteArrayBuffer(id.length + Long.SIZE /* some extra fields */, false);
+ buffer.putBytes(id);
+ buffer.putLong(fileOffset);
+ buffer.putInt(len);
+ return checkData(SftpConstants.SSH_FXP_READ, buffer, dstOffset, dst, eofSignalled);
+ }
+
+ protected int checkData(int cmd, Buffer request, int dstOffset, byte[] dst, AtomicReference<Boolean> eofSignalled) throws IOException {
+ if (eofSignalled != null) {
+ eofSignalled.set(null);
+ }
+ int reqId = send(cmd, request);
+ Buffer response = receive(reqId);
+ return checkDataResponse(cmd, response, dstOffset, dst, eofSignalled);
+ }
+
+ protected int checkDataResponse(int cmd, Buffer buffer, int dstoff, byte[] dst, AtomicReference<Boolean> eofSignalled) throws IOException {
+ if (eofSignalled != null) {
+ eofSignalled.set(null);
+ }
+
+ int length = buffer.getInt();
+ int type = buffer.getUByte();
+ int id = buffer.getInt();
+ if (type == SftpConstants.SSH_FXP_DATA) {
+ int len = buffer.getInt();
+ buffer.getRawBytes(dst, dstoff, len);
+ Boolean indicator = SftpHelper.getEndOfFileIndicatorValue(buffer, getVersion());
+ if (log.isTraceEnabled()) {
+ log.trace("checkDataResponse({}][id={}] {} offset={}, len={}, EOF={}",
+ getClientChannel(), SftpConstants.getCommandMessageName(cmd),
+ id, dstoff, len, indicator);
+ }
+ if (eofSignalled != null) {
+ eofSignalled.set(indicator);
+ }
+
+ return len;
+ }
+
+ if (type == SftpConstants.SSH_FXP_STATUS) {
+ int substatus = buffer.getInt();
+ String msg = buffer.getString();
+ String lang = buffer.getString();
+ if (log.isTraceEnabled()) {
+ log.trace("checkDataResponse({})[id={}] {} status: {} [{}] {}",
+ getClientChannel(), id, SftpConstants.getCommandMessageName(cmd),
+ SftpConstants.getStatusName(substatus), lang, msg);
+ }
+
+ if (substatus == SftpConstants.SSH_FX_EOF) {
+ return -1;
+ }
+
+ throwStatusException(cmd, id, substatus, msg, lang);
+ }
+
+ return handleUnknownDataPacket(cmd, id, type, length, buffer);
+ }
+
+ protected int handleUnknownDataPacket(int cmd, int id, int type, int length, Buffer buffer) throws IOException {
+ IOException err = handleUnexpectedPacket(cmd, SftpConstants.SSH_FXP_DATA, id, type, length, buffer);
+ if (err != null) {
+ throw err;
+ }
+
+ return 0;
+ }
+
+ @Override
+ public void write(Handle handle, long fileOffset, byte[] src, int srcOffset, int len) throws IOException {
+ // do some bounds checking first
+ if ((fileOffset < 0) || (srcOffset < 0) || (len < 0)) {
+ throw new IllegalArgumentException("write(" + handle + ") please ensure all parameters "
+ + " are non-negative values: file-offset=" + fileOffset
+ + ", src-offset=" + srcOffset + ", len=" + len);
+ }
+ if ((srcOffset + len) > src.length) {
+ throw new IllegalArgumentException("write(" + handle + ")"
+ + " cannot read bytes " + srcOffset + " to " + (srcOffset + len)
+ + " when array is only of length " + src.length);
+ }
+ if (!isOpen()) {
+ throw new IOException("write(" + handle + "/" + fileOffset + ")[" + srcOffset + "/" + len + "] client is closed");
+ }
+
+ if (log.isTraceEnabled()) {
+ log.trace("write({}) handle={}, file-offset={}, buf-offset={}, len={}",
+ getClientChannel(), handle, fileOffset, srcOffset, len);
+ }
+
+ byte[] id = Objects.requireNonNull(handle, "No handle").getIdentifier();
+ Buffer buffer = new ByteArrayBuffer(id.length + len + Long.SIZE /* some extra fields */, false);
+ buffer.putBytes(id);
+ buffer.putLong(fileOffset);
+ buffer.putBytes(src, srcOffset, len);
+ checkCommandStatus(SftpConstants.SSH_FXP_WRITE, buffer);
+ }
+
+ @Override
+ public void mkdir(String path) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("mkdir(" + path + ") client is closed");
+ }
+
+ if (log.isDebugEnabled()) {
+ log.debug("mkdir({}) {}", getClientSession(), path);
+ }
+
+ Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */, false);
+ buffer.putString(path);
+ buffer.putInt(0);
+
+ int version = getVersion();
+ if (version != SftpConstants.SFTP_V3) {
+ buffer.putByte((byte) 0);
+ }
+
+ checkCommandStatus(SftpConstants.SSH_FXP_MKDIR, buffer);
+ }
+
+ @Override
+ public void rmdir(String path) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("rmdir(" + path + ") client is closed");
+ }
+
+ if (log.isDebugEnabled()) {
+ log.debug("rmdir({}) {}", getClientSession(), path);
+ }
+
+ Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */, false);
+ buffer.putString(path);
+ checkCommandStatus(SftpConstants.SSH_FXP_RMDIR, buffer);
+ }
+
+ @Override
+ public CloseableHandle openDir(String path) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("openDir(" + path + ") client is closed");
+ }
+
+ Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */, false);
+ buffer.putString(path);
+
+ CloseableHandle handle = new DefaultCloseableHandle(this, path, checkHandle(SftpConstants.SSH_FXP_OPENDIR, buffer));
+ if (log.isTraceEnabled()) {
+ log.trace("openDir({})[{}}: {}", getClientSession(), path, handle);
+ }
+
+ return handle;
+ }
+
+ @Override
+ public List<DirEntry> readDir(Handle handle, AtomicReference<Boolean> eolIndicator) throws IOException {
+ if (eolIndicator != null) {
+ eolIndicator.set(null); // assume unknown information
+ }
+ if (!isOpen()) {
+ throw new IOException("readDir(" + handle + ") client is closed");
+ }
+
+ byte[] id = Objects.requireNonNull(handle, "No handle").getIdentifier();
+ Buffer buffer = new ByteArrayBuffer(id.length + Byte.SIZE /* some extra fields */, false);
+ buffer.putBytes(id);
+
+ int cmdId = send(SftpConstants.SSH_FXP_READDIR, buffer);
+ Buffer response = receive(cmdId);
+ return checkDirResponse(SftpConstants.SSH_FXP_READDIR, response, eolIndicator);
+ }
+
+ protected List<DirEntry> checkDirResponse(int cmd, Buffer buffer, AtomicReference<Boolean> eolIndicator) throws IOException {
+ if (eolIndicator != null) {
+ eolIndicator.set(null); // assume unknown
+ }
+
+ int length = buffer.getInt();
+ int type = buffer.getUByte();
+ int id = buffer.getInt();
+ if (type == SftpConstants.SSH_FXP_NAME) {
+ int len = buffer.getInt();
+ int version = getVersion();
+ ClientChannel channel = getClientChannel();
+ if (log.isDebugEnabled()) {
+ log.debug("checkDirResponse({}}[id={}] reading {} entries", channel, id, len);
+ }
+
+ List<DirEntry> entries = new ArrayList<>(len);
+ for (int i = 0; i < len; i++) {
+ String name = getReferencedName(cmd, buffer);
+ String longName = (version == SftpConstants.SFTP_V3) ? getReferencedName(cmd, buffer) : null;
+ Attributes attrs = readAttributes(cmd, buffer);
+ if (log.isTraceEnabled()) {
+ log.trace("checkDirResponse({})[id={}][{}] ({})[{}]: {}",
+ channel, id, i, name, longName, attrs);
+ }
+
+ entries.add(new DirEntry(name, longName, attrs));
+ }
+
+ Boolean indicator = SftpHelper.getEndOfListIndicatorValue(buffer, version);
+ if (eolIndicator != null) {
+ eolIndicator.set(indicator);
+ }
+
+ if (log.isDebugEnabled()) {
+ log.debug("checkDirResponse({}}[id={}] read count={}, eol={}", channel, entries.size(), indicator);
+ }
+ return entries;
+ }
+
+ if (type == SftpConstants.SSH_FXP_STATUS) {
+ int substatus = buffer.getInt();
+ String msg = buffer.getString();
+ String lang = buffer.getString();
+ if (log.isTraceEnabled()) {
+ log.trace("checkDirResponse({})[id={}] - status: {} [{}] {}",
+ getClientChannel(), id, SftpConstants.getStatusName(substatus), lang, msg);
+ }
+
+ if (substatus == SftpConstants.SSH_FX_EOF) {
+ return null;
+ }
+
+ throwStatusException(cmd, id, substatus, msg, lang);
+ }
+
+ return handleUnknownDirListingPacket(cmd, id, type, length, buffer);
+ }
+
+ protected List<DirEntry> handleUnknownDirListingPacket(int cmd, int id, int type, int length, Buffer buffer) throws IOException {
+ IOException err = handleUnexpectedPacket(cmd, SftpConstants.SSH_FXP_NAME, id, type, length, buffer);
+ if (err != null) {
+ throw err;
+ }
+ return Collections.emptyList();
+ }
+
+ protected IOException handleUnexpectedPacket(int cmd, int expected, int id, int type, int length, Buffer buffer) throws IOException {
+ throw new SshException("Unexpected SFTP packet received while awaiting " + SftpConstants.getCommandMessageName(expected)
+ + " response to " + SftpConstants.getCommandMessageName(cmd)
+ + ": type=" + SftpConstants.getCommandMessageName(type) + ", id=" + id + ", length=" + length);
+ }
+
+ @Override
+ public String canonicalPath(String path) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("canonicalPath(" + path + ") client is closed");
+ }
+
+ Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE, false);
+ buffer.putString(path);
+ return checkOneName(SftpConstants.SSH_FXP_REALPATH, buffer);
+ }
+
+ @Override
+ public Attributes stat(String path) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("stat(" + path + ") client is closed");
+ }
+
+ Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE, false);
+ buffer.putString(path);
+
+ int version = getVersion();
+ if (version >= SftpConstants.SFTP_V4) {
+ buffer.putInt(SftpConstants.SSH_FILEXFER_ATTR_ALL);
+ }
+
+ return checkAttributes(SftpConstants.SSH_FXP_STAT, buffer);
+ }
+
+ @Override
+ public Attributes lstat(String path) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("lstat(" + path + ") client is closed");
+ }
+
+ Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE, false);
+ buffer.putString(path);
+
+ int version = getVersion();
+ if (version >= SftpConstants.SFTP_V4) {
+ buffer.putInt(SftpConstants.SSH_FILEXFER_ATTR_ALL);
+ }
+
+ return checkAttributes(SftpConstants.SSH_FXP_LSTAT, buffer);
+ }
+
+ @Override
+ public Attributes stat(Handle handle) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("stat(" + handle + ") client is closed");
+ }
+
+ byte[] id = Objects.requireNonNull(handle, "No handle").getIdentifier();
+ Buffer buffer = new ByteArrayBuffer(id.length + Byte.SIZE /* a bit extra */, false);
+ buffer.putBytes(id);
+
+ int version = getVersion();
+ if (version >= SftpConstants.SFTP_V4) {
+ buffer.putInt(SftpConstants.SSH_FILEXFER_ATTR_ALL);
+ }
+
+ return checkAttributes(SftpConstants.SSH_FXP_FSTAT, buffer);
+ }
+
+ @Override
+ public void setStat(String path, Attributes attributes) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("setStat(" + path + ")[" + attributes + "] client is closed");
+ }
+
+ if (log.isDebugEnabled()) {
+ log.debug("setStat({})[{}]: {}", getClientSession(), path, attributes);
+ }
+
+ Buffer buffer = new ByteArrayBuffer();
+ buffer.putString(path);
+ writeAttributes(buffer, attributes);
+ checkCommandStatus(SftpConstants.SSH_FXP_SETSTAT, buffer);
+ }
+
+ @Override
+ public void setStat(Handle handle, Attributes attributes) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("setStat(" + handle + ")[" + attributes + "] client is closed");
+ }
+
+ if (log.isDebugEnabled()) {
+ log.debug("setStat({})[{}]: {}", getClientSession(), handle, attributes);
+ }
+ byte[] id = Objects.requireNonNull(handle, "No handle").getIdentifier();
+ Buffer buffer = new ByteArrayBuffer(id.length + (2 * Long.SIZE) /* some extras */, false);
+ buffer.putBytes(id);
+ writeAttributes(buffer, attributes);
+ checkCommandStatus(SftpConstants.SSH_FXP_FSETSTAT, buffer);
+ }
+
+ @Override
+ public String readLink(String path) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("readLink(" + path + ") client is closed");
+ }
+
+ Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */, false);
+ buffer.putString(path);
+ return checkOneName(SftpConstants.SSH_FXP_READLINK, buffer);
+ }
+
+ @Override
+ public void link(String linkPath, String targetPath, boolean symbolic) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("link(" + linkPath + " => " + targetPath + ")[symbolic=" + symbolic + "] client is closed");
+ }
+
+ if (log.isDebugEnabled()) {
+ log.debug("link({})[symbolic={}] {} => {}", getClientSession(), symbolic, linkPath, targetPath);
+ }
+
+ Buffer buffer = new ByteArrayBuffer(linkPath.length() + targetPath.length() + Long.SIZE /* some extra fields */, false);
+ int version = getVersion();
+ if (version < SftpConstants.SFTP_V6) {
+ if (!symbolic) {
+ throw new UnsupportedOperationException("Hard links are not supported in sftp v" + version);
+ }
+ buffer.putString(targetPath);
+ buffer.putString(linkPath);
+ checkCommandStatus(SftpConstants.SSH_FXP_SYMLINK, buffer);
+ } else {
+ buffer.putString(targetPath);
+ buffer.putString(linkPath);
+ buffer.putBoolean(symbolic);
+ checkCommandStatus(SftpConstants.SSH_FXP_LINK, buffer);
+ }
+ }
+
+ @Override
+ public void lock(Handle handle, long offset, long length, int mask) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("lock(" + handle + ")[offset=" + offset + ", length=" + length + ", mask=0x" + Integer.toHexString(mask) + "] client is closed");
+ }
+
+ if (log.isDebugEnabled()) {
+ log.debug("lock({})[{}] offset={}, length={}, mask=0x{}",
+ getClientSession(), handle, offset, length, Integer.toHexString(mask));
+ }
+
+ byte[] id = Objects.requireNonNull(handle, "No handle").getIdentifier();
+ Buffer buffer = new ByteArrayBuffer(id.length + Long.SIZE /* a bit extra */, false);
+ buffer.putBytes(id);
+ buffer.putLong(offset);
+ buffer.putLong(length);
+ buffer.putInt(mask);
+ checkCommandStatus(SftpConstants.SSH_FXP_BLOCK, buffer);
+ }
+
+ @Override
+ public void unlock(Handle handle, long offset, long length) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("unlock" + handle + ")[offset=" + offset + ", length=" + length + "] client is closed");
+ }
+
+ if (log.isDebugEnabled()) {
+ log.debug("unlock({})[{}] offset={}, length={}", getClientSession(), handle, offset, length);
+ }
+
+ byte[] id = Objects.requireNonNull(handle, "No handle").getIdentifier();
+ Buffer buffer = new ByteArrayBuffer(id.length + Long.SIZE /* a bit extra */, false);
+ buffer.putBytes(id);
+ buffer.putLong(offset);
+ buffer.putLong(length);
+ checkCommandStatus(SftpConstants.SSH_FXP_UNBLOCK, buffer);
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/AbstractSftpFileAttributeView.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/AbstractSftpFileAttributeView.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/AbstractSftpFileAttributeView.java
new file mode 100644
index 0000000..0fce423
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/AbstractSftpFileAttributeView.java
@@ -0,0 +1,92 @@
+/*
+ * 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.sshd.client.subsystem.sftp.impl;
+
+import java.io.IOException;
+import java.nio.file.LinkOption;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.nio.file.attribute.FileAttributeView;
+import java.util.Objects;
+
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpFileSystem;
+import org.apache.sshd.client.subsystem.sftp.SftpFileSystemProvider;
+import org.apache.sshd.client.subsystem.sftp.SftpPath;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.subsystem.sftp.SftpException;
+import org.apache.sshd.common.util.logging.AbstractLoggingBean;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class AbstractSftpFileAttributeView extends AbstractLoggingBean implements FileAttributeView {
+ protected final SftpFileSystemProvider provider;
+ protected final Path path;
+ protected final LinkOption[] options;
+
+ protected AbstractSftpFileAttributeView(SftpFileSystemProvider provider, Path path, LinkOption... options) {
+ this.provider = Objects.requireNonNull(provider, "No file system provider instance");
+ this.path = Objects.requireNonNull(path, "No path");
+ this.options = options;
+ }
+
+ @Override
+ public String name() {
+ return "view";
+ }
+
+ /**
+ * @return The underlying {@link SftpFileSystemProvider} used to
+ * provide the view functionality
+ */
+ public final SftpFileSystemProvider provider() {
+ return provider;
+ }
+
+ /**
+ * @return The referenced view {@link Path}
+ */
+ public final Path getPath() {
+ return path;
+ }
+
+ protected SftpClient.Attributes readRemoteAttributes() throws IOException {
+ return provider.readRemoteAttributes(provider.toSftpPath(path), options);
+ }
+
+ protected void writeRemoteAttributes(SftpClient.Attributes attrs) throws IOException {
+ SftpPath p = provider.toSftpPath(path);
+ SftpFileSystem fs = p.getFileSystem();
+ try (SftpClient client = fs.getClient()) {
+ try {
+ if (log.isDebugEnabled()) {
+ log.debug("writeRemoteAttributes({})[{}]: {}", fs, p, attrs);
+ }
+ client.setStat(p.toString(), attrs);
+ } catch (SftpException e) {
+ if (e.getStatus() == SftpConstants.SSH_FX_NO_SUCH_FILE) {
+ throw new NoSuchFileException(p.toString());
+ }
+ throw e;
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/DefaultCloseableHandle.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/DefaultCloseableHandle.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/DefaultCloseableHandle.java
new file mode 100644
index 0000000..f6597f3
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/DefaultCloseableHandle.java
@@ -0,0 +1,66 @@
+/*
+ * 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.sshd.client.subsystem.sftp.impl;
+
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpClient.CloseableHandle;
+import org.apache.sshd.common.util.ValidateUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class DefaultCloseableHandle extends CloseableHandle {
+ private final AtomicBoolean open = new AtomicBoolean(true);
+ private final SftpClient client;
+
+ public DefaultCloseableHandle(SftpClient client, String path, byte[] id) {
+ super(path, id);
+ this.client = ValidateUtils.checkNotNull(client, "No client for path=%s", path);
+ }
+
+ public final SftpClient getSftpClient() {
+ return client;
+ }
+
+ @Override
+ public boolean isOpen() {
+ return open.get();
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (open.getAndSet(false)) {
+ client.close(this);
+ }
+ }
+
+ @Override // to avoid Findbugs[EQ_DOESNT_OVERRIDE_EQUALS]
+ public int hashCode() {
+ return super.hashCode();
+ }
+
+ @Override // to avoid Findbugs[EQ_DOESNT_OVERRIDE_EQUALS]
+ public boolean equals(Object obj) {
+ return super.equals(obj);
+ }
+}
[3/3] mina-sshd git commit: [SSHD-790] Allow users to register a
custom SFTP client factory
Posted by lg...@apache.org.
[SSHD-790] Allow users to register a custom SFTP client factory
Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/15428746
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/15428746
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/15428746
Branch: refs/heads/master
Commit: 154287462cde88314cc7cd7c44978fc04a7c9b88
Parents: c5b163f
Author: Goldstein Lyor <ly...@c-b4.com>
Authored: Thu Dec 28 10:37:12 2017 +0200
Committer: Goldstein Lyor <ly...@c-b4.com>
Committed: Thu Dec 28 11:06:38 2017 +0200
----------------------------------------------------------------------
README.md | 111 ++
.../sshd/client/ClientFactoryManager.java | 2 +
.../java/org/apache/sshd/client/SshClient.java | 13 +
.../client/session/AbstractClientSession.java | 66 +-
.../sshd/client/session/ClientSession.java | 3 +-
.../subsystem/sftp/AbstractSftpClient.java | 1127 -----------------
.../sftp/AbstractSftpFileAttributeView.java | 88 --
.../subsystem/sftp/DefaultCloseableHandle.java | 65 -
.../subsystem/sftp/DefaultSftpClient.java | 461 -------
.../sftp/SftpAclFileAttributeView.java | 2 +
.../sshd/client/subsystem/sftp/SftpClient.java | 2 +-
.../subsystem/sftp/SftpClientCreator.java | 20 +-
.../subsystem/sftp/SftpClientFactory.java | 51 +
.../sftp/SftpClientFactoryManager.java | 37 +
.../client/subsystem/sftp/SftpFileSystem.java | 1 +
.../subsystem/sftp/SftpFileSystemProvider.java | 2 +-
.../sftp/SftpPosixFileAttributeView.java | 1 +
.../subsystem/sftp/impl/AbstractSftpClient.java | 1134 ++++++++++++++++++
.../impl/AbstractSftpFileAttributeView.java | 92 ++
.../sftp/impl/DefaultCloseableHandle.java | 66 +
.../subsystem/sftp/impl/DefaultSftpClient.java | 462 +++++++
.../sftp/impl/DefaultSftpClientFactory.java | 81 ++
.../sftp/DefaultCloseableHandleTest.java | 1 +
23 files changed, 2089 insertions(+), 1799 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/README.md
----------------------------------------------------------------------
diff --git a/README.md b/README.md
index fb6560b..4e99d3e 100644
--- a/README.md
+++ b/README.md
@@ -543,6 +543,74 @@ configuration key. For more advanced restrictions one needs to sub-class `SftpSu
`SftpSubsystemFactory` that uses the sub-classed code.
+### Registering a custom `SftpClientFactory`
+
+The code creates `SftpClient`-s and `SftpFileSystem`-s using a default built-in `SftpClientFactory` instance (see
+`DefaultSftpClientFactory`). Users may choose to register a custom factory in order to provide their own
+implementations - e.g., in order to override some default behavior. The custom factory may be registered either at
+the client or session level - e.g.:
+
+```java
+
+ SshClient client = ... setup client...
+ client.setSftpClientFactory(new MySuperDuperSftpClientFactory());
+
+ try (ClientSession session = client.connect(user, host, port).verify(timeout).getSession()) {
+ // override the default factory with a special one - but only for this session
+ session.setSftpClientFactory(new SpecialSessionSftpClientFactory());
+ session.addPasswordIdentity(password);
+ session.auth.verify(timeout);
+
+ try (SftpClient sftp = session.createSftpClient()) {
+ ... instance created through SpecialSessionSftpClientFactory ...
+ }
+ }
+
+```
+
+If no factory provided or factory set to _null_ then code reverts to using the default built-in one. **Note:** setting
+the factory to _null_ on the session level, simply delegates the creation to whatever factory is registered at the
+client level - default or custom.
+
+```java
+
+ SshClient client = ... setup client...
+ client.setSftpClientFactory(new MySuperDuperSftpClientFactory());
+
+ try (ClientSession session = client.connect(user, host, port).verify(timeout).getSession()) {
+ // override the default factory with a special one - but only for this session
+ session.setSftpClientFactory(new SpecialSessionSftpClientFactory());
+ session.addPasswordIdentity(password);
+ session.auth.verify(timeout);
+
+ try (SftpClient sftp = session.createSftpClient()) {
+ ... instance created through SpecialSessionSftpClientFactory ...
+ }
+
+ // revert to one from client
+ session.setSftpClientFactory(null);
+
+ try (SftpClient sftp = session.createSftpClient()) {
+ ... instance created through MySuperDuperSftpClientFactory ...
+ }
+
+ // remove client-level factory
+ client.setSftpClientFactory(null);
+
+ try (SftpClient sftp = session.createSftpClient()) {
+ ... instance created through built-in DefaultSftpClientFactory ...
+ }
+
+ // re-instate session-level factory
+ session.setSftpClientFactory(new SpecialSessionSftpClientFactory());
+
+ try (SftpClient sftp = session.createSftpClient()) {
+ ... instance created through SpecialSessionSftpClientFactory ...
+ }
+ }
+
+```
+
### Using `SftpFileSystemProvider` to create an `SftpFileSystem`
@@ -716,6 +784,49 @@ UTF-8 is used. **Note:** the value can be a charset name or a `java.nio.charset.
```
+Another option is to register a custom `SftpClientFactory` and create a `DefaultSftpClient` that overrides `getReferencedName` method:
+
+```java
+
+public class MyCustomSftpClient extends DefaultSftpClient {
+ public MyCustomSftpClient(ClientSession session) {
+ super(session);
+ }
+
+ @Override
+ protected String getReferencedName(int cmd, Buffer buf) {
+ byte[] bytes = buf.getBytes();
+ Charset cs = detectCharset(bytes);
+ return new String(bytes, cs);
+ }
+}
+
+public class MyCustomSftpClientFactory extends DefaultSftpClientFactory {
+ public MyCustomSftpClientFactory() {
+ super();
+ }
+
+ protected DefaultSftpClient createDefaultSftpClient(ClientSession session, SftpVersionSelector selector) throws IOException {
+ return MyCustomSftpClient(session);
+ }
+}
+
+ // Usage - register at client level and affect ALL SFTP interactions
+ SshClient client = ... setup/obtain an instance...
+ client.setSftpClientFactory(new MyCustomSftpClientFactory());
+
+ // Usage - selective session registration
+ SshClient client = ... setup/obtain an instance...
+ try (ClientSession session = client.connect(...)) {
+ if (...something special about the host/port/etc....) {
+ // affect only SFTP interactions for this session
+ session.setSftpClientFactory(new MyCustomSftpClientFactory());
+ }
+ }
+
+
+```
+
### Supported SFTP extensions
Both client and server support several of the SFTP extensions specified in various drafts:
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java b/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java
index 31b2a22..7627020 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java
@@ -21,6 +21,7 @@ package org.apache.sshd.client;
import org.apache.sshd.client.config.hosts.HostConfigEntryResolver;
import org.apache.sshd.client.config.keys.ClientIdentityLoader;
import org.apache.sshd.client.session.ClientProxyConnectorHolder;
+import org.apache.sshd.client.subsystem.sftp.SftpClientFactoryManager;
import org.apache.sshd.common.FactoryManager;
import org.apache.sshd.common.config.keys.FilePasswordProvider;
import org.apache.sshd.common.scp.ScpFileOpenerHolder;
@@ -33,6 +34,7 @@ import org.apache.sshd.common.scp.ScpFileOpenerHolder;
*/
public interface ClientFactoryManager
extends FactoryManager,
+ SftpClientFactoryManager,
ScpFileOpenerHolder,
ClientProxyConnectorHolder,
ClientAuthenticationManager {
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java b/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
index 0e8979e..6c5c291 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
@@ -86,6 +86,8 @@ import org.apache.sshd.client.session.ClientUserAuthServiceFactory;
import org.apache.sshd.client.session.SessionFactory;
import org.apache.sshd.client.simple.AbstractSimpleClientSessionCreator;
import org.apache.sshd.client.simple.SimpleClient;
+import org.apache.sshd.client.subsystem.sftp.SftpClientFactory;
+import org.apache.sshd.client.subsystem.sftp.impl.DefaultSftpClientFactory;
import org.apache.sshd.common.Closeable;
import org.apache.sshd.common.Factory;
import org.apache.sshd.common.FactoryManager;
@@ -211,6 +213,7 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
private FilePasswordProvider filePasswordProvider;
private PasswordIdentityProvider passwordIdentityProvider;
private ScpFileOpener scpOpener;
+ private SftpClientFactory sftpClientFactory;
private final List<Object> identities = new CopyOnWriteArrayList<>();
private final AuthenticationIdentitiesProvider identitiesProvider;
@@ -248,6 +251,16 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
}
@Override
+ public SftpClientFactory getSftpClientFactory() {
+ return (sftpClientFactory == null) ? DefaultSftpClientFactory.INSTANCE : sftpClientFactory;
+ }
+
+ @Override
+ public void setSftpClientFactory(SftpClientFactory sftpClientFactory) {
+ this.sftpClientFactory = sftpClientFactory;
+ }
+
+ @Override
public ServerKeyVerifier getServerKeyVerifier() {
return serverKeyVerifier;
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java b/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java
index b67310d..bc44d58 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java
@@ -43,10 +43,8 @@ import org.apache.sshd.client.channel.ClientChannel;
import org.apache.sshd.client.keyverifier.ServerKeyVerifier;
import org.apache.sshd.client.scp.DefaultScpClient;
import org.apache.sshd.client.scp.ScpClient;
-import org.apache.sshd.client.subsystem.sftp.DefaultSftpClient;
import org.apache.sshd.client.subsystem.sftp.SftpClient;
-import org.apache.sshd.client.subsystem.sftp.SftpFileSystem;
-import org.apache.sshd.client.subsystem.sftp.SftpFileSystemProvider;
+import org.apache.sshd.client.subsystem.sftp.SftpClientFactory;
import org.apache.sshd.client.subsystem.sftp.SftpVersionSelector;
import org.apache.sshd.common.FactoryManager;
import org.apache.sshd.common.NamedFactory;
@@ -92,6 +90,7 @@ public abstract class AbstractClientSession extends AbstractSession implements C
private ScpFileOpener scpOpener;
private SocketAddress connectAddress;
private ClientProxyConnector proxyConnector;
+ private SftpClientFactory sftpClientFactory;
protected AbstractClientSession(ClientFactoryManager factoryManager, IoSession ioSession) {
super(false, factoryManager, ioSession);
@@ -168,6 +167,16 @@ public abstract class AbstractClientSession extends AbstractSession implements C
}
@Override
+ public SftpClientFactory getSftpClientFactory() {
+ return resolveEffectiveProvider(SftpClientFactory.class, sftpClientFactory, getFactoryManager().getSftpClientFactory());
+ }
+
+ @Override
+ public void setSftpClientFactory(SftpClientFactory sftpClientFactory) {
+ this.sftpClientFactory = sftpClientFactory;
+ }
+
+ @Override
public void addPasswordIdentity(String password) {
// DO NOT USE checkNotNullOrNotEmpty SINCE IT TRIMS THE RESULT
ValidateUtils.checkTrue((password != null) && (!password.isEmpty()), "No password provided");
@@ -333,57 +342,14 @@ public abstract class AbstractClientSession extends AbstractSession implements C
@Override
public SftpClient createSftpClient(SftpVersionSelector selector) throws IOException {
- DefaultSftpClient client = new DefaultSftpClient(this);
- try {
- client.negotiateVersion(selector);
- } catch (IOException | RuntimeException e) {
- if (log.isDebugEnabled()) {
- log.debug("createSftpClient({}) failed ({}) to negotiate version: {}",
- this, e.getClass().getSimpleName(), e.getMessage());
- }
- if (log.isTraceEnabled()) {
- log.trace("createSftpClient(" + this + ") version negotiation failure details", e);
- }
-
- client.close();
- throw e;
- }
-
- return client;
- }
-
- @Override
- public FileSystem createSftpFileSystem() throws IOException {
- return createSftpFileSystem(SftpVersionSelector.CURRENT);
- }
-
- @Override
- public FileSystem createSftpFileSystem(int version) throws IOException {
- return createSftpFileSystem(SftpVersionSelector.fixedVersionSelector(version));
- }
-
- @Override
- public FileSystem createSftpFileSystem(SftpVersionSelector selector) throws IOException {
- return createSftpFileSystem(selector, SftpClient.DEFAULT_READ_BUFFER_SIZE, SftpClient.DEFAULT_WRITE_BUFFER_SIZE);
- }
-
- @Override
- public FileSystem createSftpFileSystem(int version, int readBufferSize, int writeBufferSize) throws IOException {
- return createSftpFileSystem(SftpVersionSelector.fixedVersionSelector(version), readBufferSize, writeBufferSize);
- }
-
- @Override
- public FileSystem createSftpFileSystem(int readBufferSize, int writeBufferSize) throws IOException {
- return createSftpFileSystem(SftpVersionSelector.CURRENT, readBufferSize, writeBufferSize);
+ SftpClientFactory factory = getSftpClientFactory();
+ return factory.createSftpClient(this, selector);
}
@Override
public FileSystem createSftpFileSystem(SftpVersionSelector selector, int readBufferSize, int writeBufferSize) throws IOException {
- SftpFileSystemProvider provider = new SftpFileSystemProvider((org.apache.sshd.client.SshClient) getFactoryManager(), selector);
- SftpFileSystem fs = provider.newFileSystem(this);
- fs.setReadBufferSize(readBufferSize);
- fs.setWriteBufferSize(writeBufferSize);
- return fs;
+ SftpClientFactory factory = getSftpClientFactory();
+ return factory.createSftpFileSystem(this, selector, readBufferSize, writeBufferSize);
}
@Override
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java
index 8c074f2..b807f17 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java
@@ -46,6 +46,7 @@ import org.apache.sshd.client.scp.ScpClientCreator;
import org.apache.sshd.client.session.forward.DynamicPortForwardingTracker;
import org.apache.sshd.client.session.forward.ExplicitPortForwardingTracker;
import org.apache.sshd.client.subsystem.sftp.SftpClientCreator;
+import org.apache.sshd.client.subsystem.sftp.SftpClientFactoryManager;
import org.apache.sshd.common.forward.PortForwardingManager;
import org.apache.sshd.common.future.KeyExchangeFuture;
import org.apache.sshd.common.session.Session;
@@ -82,7 +83,7 @@ import org.apache.sshd.common.util.net.SshdSocketAddress;
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public interface ClientSession
- extends Session, ScpClientCreator, SftpClientCreator,
+ extends Session, ScpClientCreator, SftpClientCreator, SftpClientFactoryManager,
ClientProxyConnectorHolder, ClientAuthenticationManager,
PortForwardingManager {
enum ClientSessionEvent {
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClient.java
deleted file mode 100644
index fd2875c..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClient.java
+++ /dev/null
@@ -1,1127 +0,0 @@
-/*
- * 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.sshd.client.subsystem.sftp;
-
-import java.io.IOException;
-import java.nio.charset.Charset;
-import java.nio.file.attribute.FileTime;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.concurrent.atomic.AtomicReference;
-
-import org.apache.sshd.client.channel.ClientChannel;
-import org.apache.sshd.client.subsystem.AbstractSubsystemClient;
-import org.apache.sshd.client.subsystem.sftp.extensions.BuiltinSftpClientExtensions;
-import org.apache.sshd.client.subsystem.sftp.extensions.SftpClientExtension;
-import org.apache.sshd.client.subsystem.sftp.extensions.SftpClientExtensionFactory;
-import org.apache.sshd.common.SshException;
-import org.apache.sshd.common.channel.Channel;
-import org.apache.sshd.common.subsystem.sftp.SftpConstants;
-import org.apache.sshd.common.subsystem.sftp.SftpException;
-import org.apache.sshd.common.subsystem.sftp.SftpHelper;
-import org.apache.sshd.common.subsystem.sftp.SftpUniversalOwnerAndGroup;
-import org.apache.sshd.common.subsystem.sftp.extensions.ParserUtils;
-import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.ValidateUtils;
-import org.apache.sshd.common.util.buffer.Buffer;
-import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
-
-
-/**
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public abstract class AbstractSftpClient extends AbstractSubsystemClient implements SftpClient, RawSftpClient {
- private final Attributes fileOpenAttributes = new Attributes();
- private final AtomicReference<Map<String, Object>> parsedExtensionsHolder = new AtomicReference<>(null);
-
- protected AbstractSftpClient() {
- fileOpenAttributes.setType(SftpConstants.SSH_FILEXFER_TYPE_REGULAR);
- }
-
- @Override
- public Channel getChannel() {
- return getClientChannel();
- }
-
- @Override
- public <E extends SftpClientExtension> E getExtension(Class<? extends E> extensionType) {
- Object instance = getExtension(BuiltinSftpClientExtensions.fromType(extensionType));
- if (instance == null) {
- return null;
- } else {
- return extensionType.cast(instance);
- }
- }
-
- @Override
- public SftpClientExtension getExtension(String extensionName) {
- return getExtension(BuiltinSftpClientExtensions.fromName(extensionName));
- }
-
- protected SftpClientExtension getExtension(SftpClientExtensionFactory factory) {
- if (factory == null) {
- return null;
- }
-
- Map<String, byte[]> extensions = getServerExtensions();
- Map<String, Object> parsed = getParsedServerExtensions(extensions);
- return factory.create(this, this, extensions, parsed);
- }
-
- protected Map<String, Object> getParsedServerExtensions() {
- return getParsedServerExtensions(getServerExtensions());
- }
-
- protected Map<String, Object> getParsedServerExtensions(Map<String, byte[]> extensions) {
- Map<String, Object> parsed = parsedExtensionsHolder.get();
- if (parsed == null) {
- parsed = ParserUtils.parse(extensions);
- if (parsed == null) {
- parsed = Collections.emptyMap();
- }
- parsedExtensionsHolder.set(parsed);
- }
-
- return parsed;
- }
-
- protected String getReferencedName(Buffer buf) {
- Charset cs = getNameDecodingCharset();
- return buf.getString(cs);
- }
-
- /**
- * Sends the specified command, waits for the response and then invokes {@link #checkResponseStatus(int, Buffer)}
- * @param cmd The command to send
- * @param request The request {@link Buffer}
- * @throws IOException If failed to send, receive or check the returned status
- * @see #send(int, Buffer)
- * @see #receive(int)
- * @see #checkResponseStatus(int, Buffer)
- */
- protected void checkCommandStatus(int cmd, Buffer request) throws IOException {
- int reqId = send(cmd, request);
- Buffer response = receive(reqId);
- checkResponseStatus(cmd, response);
- }
-
- /**
- * Checks if the incoming response is an {@code SSH_FXP_STATUS} one,
- * and if so whether the substatus is {@code SSH_FX_OK}.
- *
- * @param cmd The sent command opcode
- * @param buffer The received response {@link Buffer}
- * @throws IOException If response does not carry a status or carries
- * a bad status code
- * @see #checkResponseStatus(int, int, int, String, String)
- */
- protected void checkResponseStatus(int cmd, Buffer buffer) throws IOException {
- int length = buffer.getInt();
- int type = buffer.getUByte();
- int id = buffer.getInt();
- if (type == SftpConstants.SSH_FXP_STATUS) {
- int substatus = buffer.getInt();
- String msg = buffer.getString();
- String lang = buffer.getString();
- checkResponseStatus(cmd, id, substatus, msg, lang);
- } else {
- //noinspection ThrowableResultOfMethodCallIgnored
- handleUnexpectedPacket(cmd, SftpConstants.SSH_FXP_STATUS, id, type, length, buffer);
- }
- }
-
- /**
- * @param cmd The sent command opcode
- * @param id The request id
- * @param substatus The sub-status value
- * @param msg The message
- * @param lang The language
- * @throws IOException if the sub-status is not {@code SSH_FX_OK}
- * @see #throwStatusException(int, int, int, String, String)
- */
- protected void checkResponseStatus(int cmd, int id, int substatus, String msg, String lang) throws IOException {
- if (log.isTraceEnabled()) {
- log.trace("checkResponseStatus({})[id={}] cmd={} status={} lang={} msg={}",
- getClientChannel(), id, SftpConstants.getCommandMessageName(cmd),
- SftpConstants.getStatusName(substatus), lang, msg);
- }
-
- if (substatus != SftpConstants.SSH_FX_OK) {
- throwStatusException(cmd, id, substatus, msg, lang);
- }
- }
-
- protected void throwStatusException(int cmd, int id, int substatus, String msg, String lang) throws IOException {
- throw new SftpException(substatus, msg);
- }
-
- /**
- * @param cmd Command to be sent
- * @param request The {@link Buffer} containing the request
- * @return The received handle identifier
- * @throws IOException If failed to send/receive or process the response
- * @see #send(int, Buffer)
- * @see #receive(int)
- * @see #checkHandleResponse(int, Buffer)
- */
- protected byte[] checkHandle(int cmd, Buffer request) throws IOException {
- int reqId = send(cmd, request);
- Buffer response = receive(reqId);
- return checkHandleResponse(cmd, response);
- }
-
- protected byte[] checkHandleResponse(int cmd, Buffer buffer) throws IOException {
- int length = buffer.getInt();
- int type = buffer.getUByte();
- int id = buffer.getInt();
- if (type == SftpConstants.SSH_FXP_HANDLE) {
- return ValidateUtils.checkNotNullAndNotEmpty(buffer.getBytes(), "Null/empty handle in buffer", GenericUtils.EMPTY_OBJECT_ARRAY);
- }
-
- if (type == SftpConstants.SSH_FXP_STATUS) {
- int substatus = buffer.getInt();
- String msg = buffer.getString();
- String lang = buffer.getString();
- if (log.isTraceEnabled()) {
- log.trace("checkHandleResponse({})[id={}] {} - status: {} [{}] {}",
- getClientChannel(), id, SftpConstants.getCommandMessageName(cmd),
- SftpConstants.getStatusName(substatus), lang, msg);
- }
- throwStatusException(cmd, id, substatus, msg, lang);
- }
-
- return handleUnexpectedHandlePacket(cmd, id, type, length, buffer);
- }
-
- protected byte[] handleUnexpectedHandlePacket(int cmd, int id, int type, int length, Buffer buffer) throws IOException {
- handleUnexpectedPacket(cmd, SftpConstants.SSH_FXP_HANDLE, id, type, length, buffer);
- throw new SshException("No handling for unexpected handle packet id=" + id
- + ", type=" + SftpConstants.getCommandMessageName(type) + ", length=" + length);
- }
-
- /**
- * @param cmd Command to be sent
- * @param request Request {@link Buffer}
- * @return The decoded response {@code Attributes}
- * @throws IOException If failed to send/receive or process the response
- * @see #send(int, Buffer)
- * @see #receive(int)
- * @see #checkAttributesResponse(int, Buffer)
- */
- protected Attributes checkAttributes(int cmd, Buffer request) throws IOException {
- int reqId = send(cmd, request);
- Buffer response = receive(reqId);
- return checkAttributesResponse(cmd, response);
- }
-
- protected Attributes checkAttributesResponse(int cmd, Buffer buffer) throws IOException {
- int length = buffer.getInt();
- int type = buffer.getUByte();
- int id = buffer.getInt();
- if (type == SftpConstants.SSH_FXP_ATTRS) {
- return readAttributes(buffer);
- }
-
- if (type == SftpConstants.SSH_FXP_STATUS) {
- int substatus = buffer.getInt();
- String msg = buffer.getString();
- String lang = buffer.getString();
- if (log.isTraceEnabled()) {
- log.trace("checkAttributesResponse()[id={}] {} - status: {} [{}] {}",
- getClientChannel(), id, SftpConstants.getCommandMessageName(cmd),
- SftpConstants.getStatusName(substatus), lang, msg);
- }
- throwStatusException(cmd, id, substatus, msg, lang);
- }
-
- return handleUnexpectedAttributesPacket(cmd, id, type, length, buffer);
- }
-
- protected Attributes handleUnexpectedAttributesPacket(int cmd, int id, int type, int length, Buffer buffer) throws IOException {
- IOException err = handleUnexpectedPacket(cmd, SftpConstants.SSH_FXP_ATTRS, id, type, length, buffer);
- if (err != null) {
- throw err;
- }
-
- return null;
- }
-
- /**
- * @param cmd Command to be sent
- * @param request The request {@link Buffer}
- * @return The retrieved name
- * @throws IOException If failed to send/receive or process the response
- * @see #send(int, Buffer)
- * @see #receive(int)
- * @see #checkOneNameResponse(int, Buffer)
- */
- protected String checkOneName(int cmd, Buffer request) throws IOException {
- int reqId = send(cmd, request);
- Buffer response = receive(reqId);
- return checkOneNameResponse(cmd, response);
- }
-
- protected String checkOneNameResponse(int cmd, Buffer buffer) throws IOException {
- int length = buffer.getInt();
- int type = buffer.getUByte();
- int id = buffer.getInt();
- if (type == SftpConstants.SSH_FXP_NAME) {
- int len = buffer.getInt();
- if (len != 1) {
- throw new SshException("SFTP error: received " + len + " names instead of 1");
- }
- String name = getReferencedName(buffer);
- String longName = null;
- int version = getVersion();
- if (version == SftpConstants.SFTP_V3) {
- longName = getReferencedName(buffer);
- }
-
- Attributes attrs = readAttributes(buffer);
- Boolean indicator = SftpHelper.getEndOfListIndicatorValue(buffer, version);
- // TODO decide what to do if not-null and not TRUE
- if (log.isTraceEnabled()) {
- log.trace("checkOneNameResponse({})[id={}] {} ({})[{}] eol={}: {}",
- getClientChannel(), id, SftpConstants.getCommandMessageName(cmd),
- name, longName, indicator, attrs);
- }
- return name;
- }
-
- if (type == SftpConstants.SSH_FXP_STATUS) {
- int substatus = buffer.getInt();
- String msg = buffer.getString();
- String lang = buffer.getString();
- if (log.isTraceEnabled()) {
- log.trace("checkOneNameResponse({})[id={}] {} status: {} [{}] {}",
- getClientChannel(), id, SftpConstants.getCommandMessageName(cmd),
- SftpConstants.getStatusName(substatus), lang, msg);
- }
-
- throwStatusException(cmd, id, substatus, msg, lang);
- }
-
- return handleUnknownOneNamePacket(cmd, id, type, length, buffer);
- }
-
- protected String handleUnknownOneNamePacket(int cmd, int id, int type, int length, Buffer buffer) throws IOException {
- IOException err = handleUnexpectedPacket(cmd, SftpConstants.SSH_FXP_NAME, id, type, length, buffer);
- if (err != null) {
- throw err;
- }
-
- return null;
- }
-
- protected Attributes readAttributes(Buffer buffer) throws IOException {
- Attributes attrs = new Attributes();
- int flags = buffer.getInt();
- int version = getVersion();
- if (version == SftpConstants.SFTP_V3) {
- if ((flags & SftpConstants.SSH_FILEXFER_ATTR_SIZE) != 0) {
- attrs.setSize(buffer.getLong());
- }
- if ((flags & SftpConstants.SSH_FILEXFER_ATTR_UIDGID) != 0) {
- attrs.owner(buffer.getInt(), buffer.getInt());
- }
- if ((flags & SftpConstants.SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
- int perms = buffer.getInt();
- attrs.setPermissions(perms);
- attrs.setType(SftpHelper.permissionsToFileType(perms));
- }
-
- if ((flags & SftpConstants.SSH_FILEXFER_ATTR_ACMODTIME) != 0) {
- attrs.setAccessTime(SftpHelper.readTime(buffer, version, flags));
- attrs.setModifyTime(SftpHelper.readTime(buffer, version, flags));
- }
- } else if (version >= SftpConstants.SFTP_V4) {
- attrs.setType(buffer.getUByte());
- if ((flags & SftpConstants.SSH_FILEXFER_ATTR_SIZE) != 0) {
- attrs.setSize(buffer.getLong());
- }
-
- if ((version >= SftpConstants.SFTP_V6) && ((flags & SftpConstants.SSH_FILEXFER_ATTR_ALLOCATION_SIZE) != 0)) {
- @SuppressWarnings("unused")
- long allocSize = buffer.getLong(); // TODO handle allocation size
- }
-
- if ((flags & SftpConstants.SSH_FILEXFER_ATTR_OWNERGROUP) != 0) {
- attrs.setOwner(buffer.getString());
- attrs.setGroup(buffer.getString());
- }
- if ((flags & SftpConstants.SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
- attrs.setPermissions(buffer.getInt());
- }
-
- // update the permissions according to the type
- int perms = attrs.getPermissions();
- perms |= SftpHelper.fileTypeToPermission(attrs.getType());
- attrs.setPermissions(perms);
-
- if ((flags & SftpConstants.SSH_FILEXFER_ATTR_ACCESSTIME) != 0) {
- attrs.setAccessTime(SftpHelper.readTime(buffer, version, flags));
- }
- if ((flags & SftpConstants.SSH_FILEXFER_ATTR_CREATETIME) != 0) {
- attrs.setCreateTime(SftpHelper.readTime(buffer, version, flags));
- }
- if ((flags & SftpConstants.SSH_FILEXFER_ATTR_MODIFYTIME) != 0) {
- attrs.setModifyTime(SftpHelper.readTime(buffer, version, flags));
- }
- if ((version >= SftpConstants.SFTP_V6) && (flags & SftpConstants.SSH_FILEXFER_ATTR_CTIME) != 0) {
- @SuppressWarnings("unused")
- FileTime attrsChangedTime = SftpHelper.readTime(buffer, version, flags); // TODO the last time the file attributes were changed
- }
-
- if ((flags & SftpConstants.SSH_FILEXFER_ATTR_ACL) != 0) {
- attrs.setAcl(SftpHelper.readACLs(buffer, version));
- }
-
- if ((flags & SftpConstants.SSH_FILEXFER_ATTR_BITS) != 0) {
- @SuppressWarnings("unused")
- int bits = buffer.getInt();
- @SuppressWarnings("unused")
- int valid = 0xffffffff;
- if (version >= SftpConstants.SFTP_V6) {
- valid = buffer.getInt();
- }
- // TODO: handle attrib bits
- }
-
- if (version >= SftpConstants.SFTP_V6) {
- if ((flags & SftpConstants.SSH_FILEXFER_ATTR_TEXT_HINT) != 0) {
- @SuppressWarnings("unused")
- boolean text = buffer.getBoolean(); // TODO: handle text
- }
- if ((flags & SftpConstants.SSH_FILEXFER_ATTR_MIME_TYPE) != 0) {
- @SuppressWarnings("unused")
- String mimeType = buffer.getString(); // TODO: handle mime-type
- }
- if ((flags & SftpConstants.SSH_FILEXFER_ATTR_LINK_COUNT) != 0) {
- @SuppressWarnings("unused")
- int nlink = buffer.getInt(); // TODO: handle link-count
- }
- if ((flags & SftpConstants.SSH_FILEXFER_ATTR_UNTRANSLATED_NAME) != 0) {
- @SuppressWarnings("unused")
- String untranslated = getReferencedName(buffer); // TODO: handle untranslated-name
- }
- }
- } else {
- throw new IllegalStateException("readAttributes - unsupported version: " + version);
- }
-
- if ((flags & SftpConstants.SSH_FILEXFER_ATTR_EXTENDED) != 0) {
- attrs.setExtensions(SftpHelper.readExtensions(buffer));
- }
-
- return attrs;
- }
-
- protected void writeAttributes(Buffer buffer, Attributes attributes) throws IOException {
- int version = getVersion();
- int flagsMask = 0;
- Collection<Attribute> flags = Objects.requireNonNull(attributes, "No attributes").getFlags();
- if (version == SftpConstants.SFTP_V3) {
- for (Attribute a : flags) {
- switch (a) {
- case Size:
- flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_SIZE;
- break;
- case UidGid:
- flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_UIDGID;
- break;
- case Perms:
- flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_PERMISSIONS;
- break;
- case AccessTime:
- if (flags.contains(Attribute.ModifyTime)) {
- flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_ACMODTIME;
- }
- break;
- case ModifyTime:
- if (flags.contains(Attribute.AccessTime)) {
- flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_ACMODTIME;
- }
- break;
- case Extensions:
- flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_EXTENDED;
- break;
- default: // do nothing
- }
- }
- buffer.putInt(flagsMask);
- if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_SIZE) != 0) {
- buffer.putLong(attributes.getSize());
- }
- if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_UIDGID) != 0) {
- buffer.putInt(attributes.getUserId());
- buffer.putInt(attributes.getGroupId());
- }
- if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
- buffer.putInt(attributes.getPermissions());
- }
-
- if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_ACMODTIME) != 0) {
- SftpHelper.writeTime(buffer, version, flagsMask, attributes.getAccessTime());
- SftpHelper.writeTime(buffer, version, flagsMask, attributes.getModifyTime());
- }
- } else if (version >= SftpConstants.SFTP_V4) {
- for (Attribute a : flags) {
- switch (a) {
- case Size:
- flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_SIZE;
- break;
- case OwnerGroup:
- flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_OWNERGROUP;
- break;
- case Perms:
- flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_PERMISSIONS;
- break;
- case AccessTime:
- flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_ACCESSTIME;
- break;
- case ModifyTime:
- flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_MODIFYTIME;
- break;
- case CreateTime:
- flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_CREATETIME;
- break;
- case Acl:
- flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_ACL;
- break;
- case Extensions:
- flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_EXTENDED;
- break;
- default: // do nothing
- }
- }
- buffer.putInt(flagsMask);
- buffer.putByte((byte) attributes.getType());
- if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_SIZE) != 0) {
- buffer.putLong(attributes.getSize());
- }
- if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_OWNERGROUP) != 0) {
- String owner = attributes.getOwner();
- buffer.putString(GenericUtils.isEmpty(owner) ? SftpUniversalOwnerAndGroup.Owner.getName() : owner);
-
- String group = attributes.getGroup();
- buffer.putString(GenericUtils.isEmpty(group) ? SftpUniversalOwnerAndGroup.Group.getName() : group);
- }
- if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
- buffer.putInt(attributes.getPermissions());
- }
- if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_ACCESSTIME) != 0) {
- SftpHelper.writeTime(buffer, version, flagsMask, attributes.getAccessTime());
- }
- if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_CREATETIME) != 0) {
- SftpHelper.writeTime(buffer, version, flagsMask, attributes.getCreateTime());
- }
- if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_MODIFYTIME) != 0) {
- SftpHelper.writeTime(buffer, version, flagsMask, attributes.getModifyTime());
- }
- if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_ACL) != 0) {
- SftpHelper.writeACLs(buffer, version, attributes.getAcl());
- }
-
- // TODO: for v6+ add CTIME (see https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#page-21)
- } else {
- throw new UnsupportedOperationException("writeAttributes(" + attributes + ") unsupported version: " + version);
- }
-
- if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_EXTENDED) != 0) {
- SftpHelper.writeExtensions(buffer, attributes.getExtensions());
- }
- }
-
- @Override
- public CloseableHandle open(String path, Collection<OpenMode> options) throws IOException {
- if (!isOpen()) {
- throw new IOException("open(" + path + ")[" + options + "] client is closed");
- }
-
- /*
- * Be consistent with FileChannel#open - if no mode specified then READ is assumed
- */
- if (GenericUtils.isEmpty(options)) {
- options = EnumSet.of(OpenMode.Read);
- }
-
- Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */, false);
- buffer.putString(path);
- int version = getVersion();
- int mode = 0;
- if (version < SftpConstants.SFTP_V5) {
- for (OpenMode m : options) {
- switch (m) {
- case Read:
- mode |= SftpConstants.SSH_FXF_READ;
- break;
- case Write:
- mode |= SftpConstants.SSH_FXF_WRITE;
- break;
- case Append:
- mode |= SftpConstants.SSH_FXF_APPEND;
- break;
- case Create:
- mode |= SftpConstants.SSH_FXF_CREAT;
- break;
- case Truncate:
- mode |= SftpConstants.SSH_FXF_TRUNC;
- break;
- case Exclusive:
- mode |= SftpConstants.SSH_FXF_EXCL;
- break;
- default: // do nothing
- }
- }
- } else {
- int access = 0;
- if (options.contains(OpenMode.Read)) {
- access |= SftpConstants.ACE4_READ_DATA | SftpConstants.ACE4_READ_ATTRIBUTES;
- }
- if (options.contains(OpenMode.Write)) {
- access |= SftpConstants.ACE4_WRITE_DATA | SftpConstants.ACE4_WRITE_ATTRIBUTES;
- }
- if (options.contains(OpenMode.Append)) {
- access |= SftpConstants.ACE4_APPEND_DATA;
- }
- buffer.putInt(access);
-
- if (options.contains(OpenMode.Create) && options.contains(OpenMode.Exclusive)) {
- mode |= SftpConstants.SSH_FXF_CREATE_NEW;
- } else if (options.contains(OpenMode.Create) && options.contains(OpenMode.Truncate)) {
- mode |= SftpConstants.SSH_FXF_CREATE_TRUNCATE;
- } else if (options.contains(OpenMode.Create)) {
- mode |= SftpConstants.SSH_FXF_OPEN_OR_CREATE;
- } else if (options.contains(OpenMode.Truncate)) {
- mode |= SftpConstants.SSH_FXF_TRUNCATE_EXISTING;
- } else {
- mode |= SftpConstants.SSH_FXF_OPEN_EXISTING;
- }
- }
- buffer.putInt(mode);
- writeAttributes(buffer, fileOpenAttributes);
-
- CloseableHandle handle = new DefaultCloseableHandle(this, path, checkHandle(SftpConstants.SSH_FXP_OPEN, buffer));
- if (log.isTraceEnabled()) {
- log.trace("open({})[{}] options={}: {}", getClientSession(), path, options, handle);
- }
- return handle;
- }
-
- @Override
- public void close(Handle handle) throws IOException {
- if (!isOpen()) {
- throw new IOException("close(" + handle + ") client is closed");
- }
-
- if (log.isTraceEnabled()) {
- log.trace("close({}) {}", getClientSession(), handle);
- }
-
- byte[] id = Objects.requireNonNull(handle, "No handle").getIdentifier();
- Buffer buffer = new ByteArrayBuffer(id.length + Long.SIZE /* some extra fields */, false);
- buffer.putBytes(id);
- checkCommandStatus(SftpConstants.SSH_FXP_CLOSE, buffer);
- }
-
- @Override
- public void remove(String path) throws IOException {
- if (!isOpen()) {
- throw new IOException("remove(" + path + ") client is closed");
- }
-
- if (log.isDebugEnabled()) {
- log.debug("remove({}) {}", getClientSession(), path);
- }
-
- Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */, false);
- buffer.putString(path);
- checkCommandStatus(SftpConstants.SSH_FXP_REMOVE, buffer);
- }
-
- @Override
- public void rename(String oldPath, String newPath, Collection<CopyMode> options) throws IOException {
- if (!isOpen()) {
- throw new IOException("rename(" + oldPath + " => " + newPath + ")[" + options + "] client is closed");
- }
-
- if (log.isDebugEnabled()) {
- log.debug("rename({}) {} => {}", getClientSession(), oldPath, newPath);
- }
-
- Buffer buffer = new ByteArrayBuffer(oldPath.length() + newPath.length() + Long.SIZE /* some extra fields */, false);
- buffer.putString(oldPath);
- buffer.putString(newPath);
-
- int numOptions = GenericUtils.size(options);
- int version = getVersion();
- if (version >= SftpConstants.SFTP_V5) {
- int opts = 0;
- if (numOptions > 0) {
- for (CopyMode opt : options) {
- switch (opt) {
- case Atomic:
- opts |= SftpConstants.SSH_FXP_RENAME_ATOMIC;
- break;
- case Overwrite:
- opts |= SftpConstants.SSH_FXP_RENAME_OVERWRITE;
- break;
- default: // do nothing
- }
- }
- }
- buffer.putInt(opts);
- } else if (numOptions > 0) {
- throw new UnsupportedOperationException("rename(" + oldPath + " => " + newPath + ")"
- + " - copy options can not be used with this SFTP version: " + options);
- }
- checkCommandStatus(SftpConstants.SSH_FXP_RENAME, buffer);
- }
-
- @Override
- public int read(Handle handle, long fileOffset, byte[] dst, int dstOffset, int len, AtomicReference<Boolean> eofSignalled) throws IOException {
- if (eofSignalled != null) {
- eofSignalled.set(null);
- }
-
- if (!isOpen()) {
- throw new IOException("read(" + handle + "/" + fileOffset + ")[" + dstOffset + "/" + len + "] client is closed");
- }
-
- byte[] id = Objects.requireNonNull(handle, "No handle").getIdentifier();
- Buffer buffer = new ByteArrayBuffer(id.length + Long.SIZE /* some extra fields */, false);
- buffer.putBytes(id);
- buffer.putLong(fileOffset);
- buffer.putInt(len);
- return checkData(SftpConstants.SSH_FXP_READ, buffer, dstOffset, dst, eofSignalled);
- }
-
- protected int checkData(int cmd, Buffer request, int dstOffset, byte[] dst, AtomicReference<Boolean> eofSignalled) throws IOException {
- if (eofSignalled != null) {
- eofSignalled.set(null);
- }
- int reqId = send(cmd, request);
- Buffer response = receive(reqId);
- return checkDataResponse(cmd, response, dstOffset, dst, eofSignalled);
- }
-
- protected int checkDataResponse(int cmd, Buffer buffer, int dstoff, byte[] dst, AtomicReference<Boolean> eofSignalled) throws IOException {
- if (eofSignalled != null) {
- eofSignalled.set(null);
- }
-
- int length = buffer.getInt();
- int type = buffer.getUByte();
- int id = buffer.getInt();
- if (type == SftpConstants.SSH_FXP_DATA) {
- int len = buffer.getInt();
- buffer.getRawBytes(dst, dstoff, len);
- Boolean indicator = SftpHelper.getEndOfFileIndicatorValue(buffer, getVersion());
- if (log.isTraceEnabled()) {
- log.trace("checkDataResponse({}][id={}] {} offset={}, len={}, EOF={}",
- getClientChannel(), SftpConstants.getCommandMessageName(cmd),
- id, dstoff, len, indicator);
- }
- if (eofSignalled != null) {
- eofSignalled.set(indicator);
- }
-
- return len;
- }
-
- if (type == SftpConstants.SSH_FXP_STATUS) {
- int substatus = buffer.getInt();
- String msg = buffer.getString();
- String lang = buffer.getString();
- if (log.isTraceEnabled()) {
- log.trace("checkDataResponse({})[id={}] {} status: {} [{}] {}",
- getClientChannel(), id, SftpConstants.getCommandMessageName(cmd),
- SftpConstants.getStatusName(substatus), lang, msg);
- }
-
- if (substatus == SftpConstants.SSH_FX_EOF) {
- return -1;
- }
-
- throwStatusException(cmd, id, substatus, msg, lang);
- }
-
- return handleUnknownDataPacket(cmd, id, type, length, buffer);
- }
-
- protected int handleUnknownDataPacket(int cmd, int id, int type, int length, Buffer buffer) throws IOException {
- IOException err = handleUnexpectedPacket(cmd, SftpConstants.SSH_FXP_DATA, id, type, length, buffer);
- if (err != null) {
- throw err;
- }
-
- return 0;
- }
-
- @Override
- public void write(Handle handle, long fileOffset, byte[] src, int srcOffset, int len) throws IOException {
- // do some bounds checking first
- if ((fileOffset < 0) || (srcOffset < 0) || (len < 0)) {
- throw new IllegalArgumentException("write(" + handle + ") please ensure all parameters "
- + " are non-negative values: file-offset=" + fileOffset
- + ", src-offset=" + srcOffset + ", len=" + len);
- }
- if ((srcOffset + len) > src.length) {
- throw new IllegalArgumentException("write(" + handle + ")"
- + " cannot read bytes " + srcOffset + " to " + (srcOffset + len)
- + " when array is only of length " + src.length);
- }
- if (!isOpen()) {
- throw new IOException("write(" + handle + "/" + fileOffset + ")[" + srcOffset + "/" + len + "] client is closed");
- }
-
- if (log.isTraceEnabled()) {
- log.trace("write({}) handle={}, file-offset={}, buf-offset={}, len={}",
- getClientChannel(), handle, fileOffset, srcOffset, len);
- }
-
- byte[] id = Objects.requireNonNull(handle, "No handle").getIdentifier();
- Buffer buffer = new ByteArrayBuffer(id.length + len + Long.SIZE /* some extra fields */, false);
- buffer.putBytes(id);
- buffer.putLong(fileOffset);
- buffer.putBytes(src, srcOffset, len);
- checkCommandStatus(SftpConstants.SSH_FXP_WRITE, buffer);
- }
-
- @Override
- public void mkdir(String path) throws IOException {
- if (!isOpen()) {
- throw new IOException("mkdir(" + path + ") client is closed");
- }
-
- if (log.isDebugEnabled()) {
- log.debug("mkdir({}) {}", getClientSession(), path);
- }
-
- Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */, false);
- buffer.putString(path);
- buffer.putInt(0);
-
- int version = getVersion();
- if (version != SftpConstants.SFTP_V3) {
- buffer.putByte((byte) 0);
- }
-
- checkCommandStatus(SftpConstants.SSH_FXP_MKDIR, buffer);
- }
-
- @Override
- public void rmdir(String path) throws IOException {
- if (!isOpen()) {
- throw new IOException("rmdir(" + path + ") client is closed");
- }
-
- if (log.isDebugEnabled()) {
- log.debug("rmdir({}) {}", getClientSession(), path);
- }
-
- Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */, false);
- buffer.putString(path);
- checkCommandStatus(SftpConstants.SSH_FXP_RMDIR, buffer);
- }
-
- @Override
- public CloseableHandle openDir(String path) throws IOException {
- if (!isOpen()) {
- throw new IOException("openDir(" + path + ") client is closed");
- }
-
- Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */, false);
- buffer.putString(path);
-
- CloseableHandle handle = new DefaultCloseableHandle(this, path, checkHandle(SftpConstants.SSH_FXP_OPENDIR, buffer));
- if (log.isTraceEnabled()) {
- log.trace("openDir({})[{}}: {}", getClientSession(), path, handle);
- }
-
- return handle;
- }
-
- @Override
- public List<DirEntry> readDir(Handle handle, AtomicReference<Boolean> eolIndicator) throws IOException {
- if (eolIndicator != null) {
- eolIndicator.set(null); // assume unknown information
- }
- if (!isOpen()) {
- throw new IOException("readDir(" + handle + ") client is closed");
- }
-
- byte[] id = Objects.requireNonNull(handle, "No handle").getIdentifier();
- Buffer buffer = new ByteArrayBuffer(id.length + Byte.SIZE /* some extra fields */, false);
- buffer.putBytes(id);
-
- int cmdId = send(SftpConstants.SSH_FXP_READDIR, buffer);
- Buffer response = receive(cmdId);
- return checkDirResponse(SftpConstants.SSH_FXP_READDIR, response, eolIndicator);
- }
-
- protected List<DirEntry> checkDirResponse(int cmd, Buffer buffer, AtomicReference<Boolean> eolIndicator) throws IOException {
- if (eolIndicator != null) {
- eolIndicator.set(null); // assume unknown
- }
-
- int length = buffer.getInt();
- int type = buffer.getUByte();
- int id = buffer.getInt();
- if (type == SftpConstants.SSH_FXP_NAME) {
- int len = buffer.getInt();
- int version = getVersion();
- ClientChannel channel = getClientChannel();
- if (log.isDebugEnabled()) {
- log.debug("checkDirResponse({}}[id={}] reading {} entries", channel, id, len);
- }
-
- List<DirEntry> entries = new ArrayList<>(len);
- for (int i = 0; i < len; i++) {
- String name = getReferencedName(buffer);
- String longName = (version == SftpConstants.SFTP_V3) ? getReferencedName(buffer) : null;
- Attributes attrs = readAttributes(buffer);
- if (log.isTraceEnabled()) {
- log.trace("checkDirResponse({})[id={}][{}] ({})[{}]: {}",
- channel, id, i, name, longName, attrs);
- }
-
- entries.add(new DirEntry(name, longName, attrs));
- }
-
- Boolean indicator = SftpHelper.getEndOfListIndicatorValue(buffer, version);
- if (eolIndicator != null) {
- eolIndicator.set(indicator);
- }
-
- if (log.isDebugEnabled()) {
- log.debug("checkDirResponse({}}[id={}] read count={}, eol={}", channel, entries.size(), indicator);
- }
- return entries;
- }
-
- if (type == SftpConstants.SSH_FXP_STATUS) {
- int substatus = buffer.getInt();
- String msg = buffer.getString();
- String lang = buffer.getString();
- if (log.isTraceEnabled()) {
- log.trace("checkDirResponse({})[id={}] - status: {} [{}] {}",
- getClientChannel(), id, SftpConstants.getStatusName(substatus), lang, msg);
- }
-
- if (substatus == SftpConstants.SSH_FX_EOF) {
- return null;
- }
-
- throwStatusException(cmd, id, substatus, msg, lang);
- }
-
- return handleUnknownDirListingPacket(cmd, id, type, length, buffer);
- }
-
- protected List<DirEntry> handleUnknownDirListingPacket(int cmd, int id, int type, int length, Buffer buffer) throws IOException {
- IOException err = handleUnexpectedPacket(cmd, SftpConstants.SSH_FXP_NAME, id, type, length, buffer);
- if (err != null) {
- throw err;
- }
- return Collections.emptyList();
- }
-
- protected IOException handleUnexpectedPacket(int cmd, int expected, int id, int type, int length, Buffer buffer) throws IOException {
- throw new SshException("Unexpected SFTP packet received while awaiting " + SftpConstants.getCommandMessageName(expected)
- + " response to " + SftpConstants.getCommandMessageName(cmd)
- + ": type=" + SftpConstants.getCommandMessageName(type) + ", id=" + id + ", length=" + length);
- }
-
- @Override
- public String canonicalPath(String path) throws IOException {
- if (!isOpen()) {
- throw new IOException("canonicalPath(" + path + ") client is closed");
- }
-
- Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE, false);
- buffer.putString(path);
- return checkOneName(SftpConstants.SSH_FXP_REALPATH, buffer);
- }
-
- @Override
- public Attributes stat(String path) throws IOException {
- if (!isOpen()) {
- throw new IOException("stat(" + path + ") client is closed");
- }
-
- Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE, false);
- buffer.putString(path);
-
- int version = getVersion();
- if (version >= SftpConstants.SFTP_V4) {
- buffer.putInt(SftpConstants.SSH_FILEXFER_ATTR_ALL);
- }
-
- return checkAttributes(SftpConstants.SSH_FXP_STAT, buffer);
- }
-
- @Override
- public Attributes lstat(String path) throws IOException {
- if (!isOpen()) {
- throw new IOException("lstat(" + path + ") client is closed");
- }
-
- Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE, false);
- buffer.putString(path);
-
- int version = getVersion();
- if (version >= SftpConstants.SFTP_V4) {
- buffer.putInt(SftpConstants.SSH_FILEXFER_ATTR_ALL);
- }
-
- return checkAttributes(SftpConstants.SSH_FXP_LSTAT, buffer);
- }
-
- @Override
- public Attributes stat(Handle handle) throws IOException {
- if (!isOpen()) {
- throw new IOException("stat(" + handle + ") client is closed");
- }
-
- byte[] id = Objects.requireNonNull(handle, "No handle").getIdentifier();
- Buffer buffer = new ByteArrayBuffer(id.length + Byte.SIZE /* a bit extra */, false);
- buffer.putBytes(id);
-
- int version = getVersion();
- if (version >= SftpConstants.SFTP_V4) {
- buffer.putInt(SftpConstants.SSH_FILEXFER_ATTR_ALL);
- }
-
- return checkAttributes(SftpConstants.SSH_FXP_FSTAT, buffer);
- }
-
- @Override
- public void setStat(String path, Attributes attributes) throws IOException {
- if (!isOpen()) {
- throw new IOException("setStat(" + path + ")[" + attributes + "] client is closed");
- }
-
- if (log.isDebugEnabled()) {
- log.debug("setStat({})[{}]: {}", getClientSession(), path, attributes);
- }
-
- Buffer buffer = new ByteArrayBuffer();
- buffer.putString(path);
- writeAttributes(buffer, attributes);
- checkCommandStatus(SftpConstants.SSH_FXP_SETSTAT, buffer);
- }
-
- @Override
- public void setStat(Handle handle, Attributes attributes) throws IOException {
- if (!isOpen()) {
- throw new IOException("setStat(" + handle + ")[" + attributes + "] client is closed");
- }
-
- if (log.isDebugEnabled()) {
- log.debug("setStat({})[{}]: {}", getClientSession(), handle, attributes);
- }
- byte[] id = Objects.requireNonNull(handle, "No handle").getIdentifier();
- Buffer buffer = new ByteArrayBuffer(id.length + (2 * Long.SIZE) /* some extras */, false);
- buffer.putBytes(id);
- writeAttributes(buffer, attributes);
- checkCommandStatus(SftpConstants.SSH_FXP_FSETSTAT, buffer);
- }
-
- @Override
- public String readLink(String path) throws IOException {
- if (!isOpen()) {
- throw new IOException("readLink(" + path + ") client is closed");
- }
-
- Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */, false);
- buffer.putString(path);
- return checkOneName(SftpConstants.SSH_FXP_READLINK, buffer);
- }
-
- @Override
- public void link(String linkPath, String targetPath, boolean symbolic) throws IOException {
- if (!isOpen()) {
- throw new IOException("link(" + linkPath + " => " + targetPath + ")[symbolic=" + symbolic + "] client is closed");
- }
-
- if (log.isDebugEnabled()) {
- log.debug("link({})[symbolic={}] {} => {}", getClientSession(), symbolic, linkPath, targetPath);
- }
-
- Buffer buffer = new ByteArrayBuffer(linkPath.length() + targetPath.length() + Long.SIZE /* some extra fields */, false);
- int version = getVersion();
- if (version < SftpConstants.SFTP_V6) {
- if (!symbolic) {
- throw new UnsupportedOperationException("Hard links are not supported in sftp v" + version);
- }
- buffer.putString(targetPath);
- buffer.putString(linkPath);
- checkCommandStatus(SftpConstants.SSH_FXP_SYMLINK, buffer);
- } else {
- buffer.putString(targetPath);
- buffer.putString(linkPath);
- buffer.putBoolean(symbolic);
- checkCommandStatus(SftpConstants.SSH_FXP_LINK, buffer);
- }
- }
-
- @Override
- public void lock(Handle handle, long offset, long length, int mask) throws IOException {
- if (!isOpen()) {
- throw new IOException("lock(" + handle + ")[offset=" + offset + ", length=" + length + ", mask=0x" + Integer.toHexString(mask) + "] client is closed");
- }
-
- if (log.isDebugEnabled()) {
- log.debug("lock({})[{}] offset={}, length={}, mask=0x{}",
- getClientSession(), handle, offset, length, Integer.toHexString(mask));
- }
-
- byte[] id = Objects.requireNonNull(handle, "No handle").getIdentifier();
- Buffer buffer = new ByteArrayBuffer(id.length + Long.SIZE /* a bit extra */, false);
- buffer.putBytes(id);
- buffer.putLong(offset);
- buffer.putLong(length);
- buffer.putInt(mask);
- checkCommandStatus(SftpConstants.SSH_FXP_BLOCK, buffer);
- }
-
- @Override
- public void unlock(Handle handle, long offset, long length) throws IOException {
- if (!isOpen()) {
- throw new IOException("unlock" + handle + ")[offset=" + offset + ", length=" + length + "] client is closed");
- }
-
- if (log.isDebugEnabled()) {
- log.debug("unlock({})[{}] offset={}, length={}", getClientSession(), handle, offset, length);
- }
-
- byte[] id = Objects.requireNonNull(handle, "No handle").getIdentifier();
- Buffer buffer = new ByteArrayBuffer(id.length + Long.SIZE /* a bit extra */, false);
- buffer.putBytes(id);
- buffer.putLong(offset);
- buffer.putLong(length);
- checkCommandStatus(SftpConstants.SSH_FXP_UNBLOCK, buffer);
- }
-}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpFileAttributeView.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpFileAttributeView.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpFileAttributeView.java
deleted file mode 100644
index 71b42a3..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpFileAttributeView.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * 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.sshd.client.subsystem.sftp;
-
-import java.io.IOException;
-import java.nio.file.LinkOption;
-import java.nio.file.NoSuchFileException;
-import java.nio.file.Path;
-import java.nio.file.attribute.FileAttributeView;
-import java.util.Objects;
-
-import org.apache.sshd.common.subsystem.sftp.SftpConstants;
-import org.apache.sshd.common.subsystem.sftp.SftpException;
-import org.apache.sshd.common.util.logging.AbstractLoggingBean;
-
-/**
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public abstract class AbstractSftpFileAttributeView extends AbstractLoggingBean implements FileAttributeView {
- protected final SftpFileSystemProvider provider;
- protected final Path path;
- protected final LinkOption[] options;
-
- protected AbstractSftpFileAttributeView(SftpFileSystemProvider provider, Path path, LinkOption... options) {
- this.provider = Objects.requireNonNull(provider, "No file system provider instance");
- this.path = Objects.requireNonNull(path, "No path");
- this.options = options;
- }
-
- @Override
- public String name() {
- return "view";
- }
-
- /**
- * @return The underlying {@link SftpFileSystemProvider} used to
- * provide the view functionality
- */
- public final SftpFileSystemProvider provider() {
- return provider;
- }
-
- /**
- * @return The referenced view {@link Path}
- */
- public final Path getPath() {
- return path;
- }
-
- protected SftpClient.Attributes readRemoteAttributes() throws IOException {
- return provider.readRemoteAttributes(provider.toSftpPath(path), options);
- }
-
- protected void writeRemoteAttributes(SftpClient.Attributes attrs) throws IOException {
- SftpPath p = provider.toSftpPath(path);
- SftpFileSystem fs = p.getFileSystem();
- try (SftpClient client = fs.getClient()) {
- try {
- if (log.isDebugEnabled()) {
- log.debug("writeRemoteAttributes({})[{}]: {}", fs, p, attrs);
- }
- client.setStat(p.toString(), attrs);
- } catch (SftpException e) {
- if (e.getStatus() == SftpConstants.SSH_FX_NO_SUCH_FILE) {
- throw new NoSuchFileException(p.toString());
- }
- throw e;
- }
- }
- }
-}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandle.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandle.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandle.java
deleted file mode 100644
index 67ad906..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandle.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * 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.sshd.client.subsystem.sftp;
-
-import java.io.IOException;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.apache.sshd.client.subsystem.sftp.SftpClient.CloseableHandle;
-import org.apache.sshd.common.util.ValidateUtils;
-
-/**
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class DefaultCloseableHandle extends CloseableHandle {
- private final AtomicBoolean open = new AtomicBoolean(true);
- private final SftpClient client;
-
- public DefaultCloseableHandle(SftpClient client, String path, byte[] id) {
- super(path, id);
- this.client = ValidateUtils.checkNotNull(client, "No client for path=%s", path);
- }
-
- public final SftpClient getSftpClient() {
- return client;
- }
-
- @Override
- public boolean isOpen() {
- return open.get();
- }
-
- @Override
- public void close() throws IOException {
- if (open.getAndSet(false)) {
- client.close(this);
- }
- }
-
- @Override // to avoid Findbugs[EQ_DOESNT_OVERRIDE_EQUALS]
- public int hashCode() {
- return super.hashCode();
- }
-
- @Override // to avoid Findbugs[EQ_DOESNT_OVERRIDE_EQUALS]
- public boolean equals(Object obj) {
- return super.equals(obj);
- }
-}