Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.apache.sshd.sftp.client.impl.AbstractSftpClient Maven / Gradle / Ivy
/*
* 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.sftp.client.impl;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.channels.FileChannel;
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.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.sshd.client.channel.ClientChannel;
import org.apache.sshd.client.subsystem.AbstractSubsystemClient;
import org.apache.sshd.common.SshConstants;
import org.apache.sshd.common.SshException;
import org.apache.sshd.common.channel.Channel;
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;
import org.apache.sshd.sftp.SftpModuleProperties;
import org.apache.sshd.sftp.client.FullAccessSftpClient;
import org.apache.sshd.sftp.client.SftpErrorDataHandler;
import org.apache.sshd.sftp.client.extensions.BuiltinSftpClientExtensions;
import org.apache.sshd.sftp.client.extensions.SftpClientExtension;
import org.apache.sshd.sftp.client.extensions.SftpClientExtensionFactory;
import org.apache.sshd.sftp.common.SftpConstants;
import org.apache.sshd.sftp.common.SftpException;
import org.apache.sshd.sftp.common.SftpHelper;
import org.apache.sshd.sftp.common.extensions.ParserUtils;
/**
* @author Apache MINA SSHD Project
*/
public abstract class AbstractSftpClient
extends AbstractSubsystemClient
implements FullAccessSftpClient, SftpErrorDataHandler {
public static final int INIT_COMMAND_SIZE = Byte.BYTES /* command */ + Integer.BYTES /* version */;
protected final SftpErrorDataHandler errorDataHandler;
private final Attributes fileOpenAttributes = new Attributes();
private final AtomicReference> parsedExtensionsHolder = new AtomicReference<>(null);
protected AbstractSftpClient(SftpErrorDataHandler delegateHandler) {
errorDataHandler = (delegateHandler == null) ? SftpErrorDataHandler.EMPTY : delegateHandler;
fileOpenAttributes.setType(SftpConstants.SSH_FILEXFER_TYPE_REGULAR);
}
@Override
public Channel getChannel() {
return getClientChannel();
}
@Override
public 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(SftpClientExtensionFactory factory) {
if (factory == null) {
return null;
}
Map extensions = getServerExtensions();
Map parsed = getParsedServerExtensions(extensions);
return factory.create(this, this, extensions, parsed);
}
protected Map getParsedServerExtensions() {
return getParsedServerExtensions(getServerExtensions());
}
protected Map getParsedServerExtensions(Map extensions) {
Map 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
* @param nameIndex The zero-based order of the requested names for the command - e.g.,
*
* When listing a directory's contents each successive name will have an increasing index.
*
*
* For SFTP version 3, when retrieving a single name, short name will have index=0 and the
* long one index=1.
*
* @return The decoded referenced name
*/
protected String getReferencedName(int cmd, Buffer buf, int nameIndex) {
Charset cs = getNameDecodingCharset();
return buf.getString(cs);
}
/**
* @param Type of {@link Buffer} being updated
* @param cmd The command for which this name is being added
* @param buf The buffer instance to update
* @param name The name to place in the buffer
* @param nameIndex The zero-based order of the name for the specific command if more than one name required -
* e.g., rename, link/symbolic link
* @return The updated buffer
*/
protected B putReferencedName(int cmd, B buf, String name, int nameIndex) {
Charset cs = getNameDecodingCharset();
buf.putString(name, cs);
return buf;
}
/**
* 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();
validateIncomingResponse(cmd, id, type, length, buffer);
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 {
IOException err = handleUnexpectedPacket(cmd, SftpConstants.SSH_FXP_STATUS, id, type, length, buffer);
if (err != null) {
throw err;
}
}
}
/**
* @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();
validateIncomingResponse(cmd, id, type, length, buffer);
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 {
IOException err = handleUnexpectedPacket(cmd, SftpConstants.SSH_FXP_HANDLE, id, type, length, buffer);
if (err != null) {
throw err;
}
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();
validateIncomingResponse(cmd, id, type, length, buffer);
if (type == SftpConstants.SSH_FXP_ATTRS) {
return readAttributes(cmd, buffer, new AtomicInteger(0));
}
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();
validateIncomingResponse(cmd, id, type, length, buffer);
if (type == SftpConstants.SSH_FXP_NAME) {
int len = buffer.getInt();
if (len != 1) {
throw new SshException("SFTP error: received " + len + " names instead of 1");
}
AtomicInteger nameIndex = new AtomicInteger(0);
String name = getReferencedName(cmd, buffer, nameIndex.getAndIncrement());
String longName = null;
int version = getVersion();
if (version == SftpConstants.SFTP_V3) {
longName = getReferencedName(cmd, buffer, nameIndex.getAndIncrement());
}
Attributes attrs = readAttributes(cmd, buffer, nameIndex);
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, AtomicInteger nameIndex) 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, nameIndex.getAndIncrement()); // 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 B writeAttributes(int cmd, B buffer, Attributes attributes) {
return SftpHelper.writeAttributes(buffer, attributes, getVersion());
}
@Override
public CloseableHandle open(String path, Collection 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 = putReferencedName(SftpConstants.SSH_FXP_OPEN, buffer, path, 0);
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;
mode |= SftpConstants.SSH_FXF_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);
buffer = writeAttributes(SftpConstants.SSH_FXP_OPEN, buffer, fileOpenAttributes);
CloseableHandle handle = new DefaultCloseableHandle(this, path, checkHandle(SftpConstants.SSH_FXP_OPEN, buffer));
if (log.isTraceEnabled()) {
log.trace("open({})[{}] options={}: {}", getClientChannel(), 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({}) {}", getClientChannel(), 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({}) {}", getClientChannel(), path);
}
Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */, false);
buffer = putReferencedName(SftpConstants.SSH_FXP_REMOVE, buffer, path, 0);
checkCommandStatus(SftpConstants.SSH_FXP_REMOVE, buffer);
}
@Override
public void rename(String oldPath, String newPath, Collection options) throws IOException {
if (!isOpen()) {
throw new IOException("rename(" + oldPath + " => " + newPath + ")[" + options + "] client is closed");
}
if (log.isDebugEnabled()) {
log.debug("rename({}) {} => {}", getClientChannel(), oldPath, newPath);
}
Buffer buffer = new ByteArrayBuffer(oldPath.length() + newPath.length() + Long.SIZE /* some extra fields */, false);
buffer = putReferencedName(SftpConstants.SSH_FXP_RENAME, buffer, oldPath, 0);
buffer = putReferencedName(SftpConstants.SSH_FXP_RENAME, buffer, newPath, 1);
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 eofSignalled)
throws IOException {
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.putUInt(len);
return checkData(SftpConstants.SSH_FXP_READ, buffer, dstOffset, dst, eofSignalled);
}
protected int checkData(
int cmd, Buffer request, int dstOffset, byte[] dst, AtomicReference eofSignalled)
throws IOException {
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 eofSignalled)
throws IOException {
int length = buffer.getInt();
int type = buffer.getUByte();
int id = buffer.getInt();
validateIncomingResponse(cmd, id, type, length, buffer);
if (type == SftpConstants.SSH_FXP_DATA) {
int len = buffer.getInt();
ValidateUtils.checkTrue(len >= 0, "Invalid response data len: %d", len);
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 errorData(byte[] buf, int start, int len) throws IOException {
/*
* The protocol does not specify how to handle such data but we are lenient and ignore it - similar to
* /dev/null
*/
if (errorDataHandler != null) {
errorDataHandler.errorData(buf, start, len);
}
}
@Override
public void write(Handle handle, long fileOffset, byte[] src, int srcOffset, int len) throws IOException {
// do some bounds checking first
if ((fileOffset < 0L) || (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");
}
boolean traceEnabled = log.isTraceEnabled();
Channel clientChannel = getClientChannel();
int chunkSize = SftpModuleProperties.WRITE_CHUNK_SIZE.getRequired(clientChannel);
ValidateUtils.checkState(chunkSize > ByteArrayBuffer.DEFAULT_SIZE, "Write chunk size too small: %d", chunkSize);
byte[] id = Objects.requireNonNull(handle, "No handle").getIdentifier();
// NOTE: we don't want to filter out zero-length write requests
int remLen = len;
do {
int writeSize = Math.min(remLen, chunkSize);
Buffer buffer = new ByteArrayBuffer(id.length + writeSize + Long.SIZE /* some extra fields */, false);
buffer.putBytes(id);
buffer.putLong(fileOffset);
buffer.putBytes(src, srcOffset, writeSize);
if (traceEnabled) {
log.trace("write({}) handle={}, file-offset={}, buf-offset={}, writeSize={}, remLen={}",
clientChannel, handle, fileOffset, srcOffset, writeSize, remLen - writeSize);
}
checkCommandStatus(SftpConstants.SSH_FXP_WRITE, buffer);
fileOffset += writeSize;
srcOffset += writeSize;
remLen -= writeSize;
} while (remLen > 0);
}
@Override
public void mkdir(String path) throws IOException {
if (!isOpen()) {
throw new IOException("mkdir(" + path + ") client is closed");
}
if (log.isDebugEnabled()) {
log.debug("mkdir({}) {}", getClientChannel(), path);
}
Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */, false);
buffer = putReferencedName(SftpConstants.SSH_FXP_MKDIR, buffer, path, 0);
buffer.putUInt(0L);
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({}) {}", getClientChannel(), path);
}
Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */, false);
buffer = putReferencedName(SftpConstants.SSH_FXP_RMDIR, buffer, path, 0);
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 = putReferencedName(SftpConstants.SSH_FXP_OPENDIR, buffer, path, 0);
CloseableHandle handle = new DefaultCloseableHandle(this, path, checkHandle(SftpConstants.SSH_FXP_OPENDIR, buffer));
if (log.isTraceEnabled()) {
log.trace("openDir({})[{}]: {}", getClientChannel(), path, handle);
}
return handle;
}
@Override
public List readDir(Handle handle, AtomicReference 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 checkDirResponse(int cmd, Buffer buffer, AtomicReference eolIndicator)
throws IOException {
if (eolIndicator != null) {
eolIndicator.set(null); // assume unknown
}
int length = buffer.getInt();
int type = buffer.getUByte();
int id = buffer.getInt();
validateIncomingResponse(cmd, id, type, length, buffer);
boolean traceEnabled = log.isTraceEnabled();
if (type == SftpConstants.SSH_FXP_NAME) {
ClientChannel channel = getClientChannel();
int count = buffer.getInt();
int version = getVersion();
// Protect against malicious or corrupted packets
if ((count < 0) || (count > SshConstants.SSH_REQUIRED_PAYLOAD_PACKET_LENGTH_SUPPORT)) {
log.error("checkDirResponse({})[id={}] illogical dir entries count: {}", channel, id, count);
throw new SshException("Illogical dir entries count: " + count);
}
boolean debugEnabled = log.isDebugEnabled();
if (debugEnabled) {
log.debug("checkDirResponse({})[id={}] reading {} entries", channel, id, count);
}
List entries = new ArrayList<>(count);
AtomicInteger nameIndex = new AtomicInteger(0);
for (int index = 1; index <= count; index++) {
String name = getReferencedName(cmd, buffer, nameIndex.getAndIncrement());
String longName = null;
if (version == SftpConstants.SFTP_V3) {
longName = getReferencedName(cmd, buffer, nameIndex.getAndIncrement());
}
Attributes attrs = readAttributes(cmd, buffer, nameIndex);
if (traceEnabled) {
log.trace("checkDirResponse({})[id={}][{}/{}] ({})[{}]: {}",
channel, id, index, count, name, longName, attrs);
}
entries.add(new DirEntry(name, longName, attrs));
}
Boolean indicator = SftpHelper.getEndOfListIndicatorValue(buffer, version);
if (eolIndicator != null) {
eolIndicator.set(indicator);
}
if (debugEnabled) {
log.debug("checkDirResponse({})[id={}] read count={}, eol={}",
channel, id, entries.size(), indicator);
}
return entries;
}
if (type == SftpConstants.SSH_FXP_STATUS) {
int substatus = buffer.getInt();
String msg = buffer.getString();
String lang = buffer.getString();
if (traceEnabled) {
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 void validateIncomingResponse(
int cmd, int id, int type, int length, Buffer buffer)
throws IOException {
int remaining = buffer.available();
if ((length < 0) || (length > (remaining + 5 /* type + id */))) {
throw new SshException(
"Bad length (" + length + ") for remaining data (" + remaining + ")"
+ " in response to " + SftpConstants.getCommandMessageName(cmd)
+ ": type=" + SftpConstants.getCommandMessageName(type) + ", id=" + id);
}
}
protected List 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();
}
/**
* @param cmd The initial command sent
* @param expected The expected packet type
* @param id The reported identifier
* @param type The reported SFTP response type
* @param length The packet length
* @param buffer The {@link Buffer} after reading from it whatever data led to this call
* @return The exception to throw - if {@code null} then implementor assumed to handle the exception
* internal. Otherwise, the exception is re-thrown
* @throws IOException If failed to handle the exception internally
*/
protected IOException handleUnexpectedPacket(
int cmd, int expected, int id, int type, int length, Buffer buffer)
throws IOException {
return 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 = putReferencedName(SftpConstants.SSH_FXP_REALPATH, buffer, path, 0);
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 = putReferencedName(SftpConstants.SSH_FXP_STAT, buffer, path, 0);
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 = putReferencedName(SftpConstants.SSH_FXP_LSTAT, buffer, path, 0);
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({})[{}]: {}", getClientChannel(), path, attributes);
}
Buffer buffer = new ByteArrayBuffer();
buffer = putReferencedName(SftpConstants.SSH_FXP_SETSTAT, buffer, path, 0);
buffer = writeAttributes(SftpConstants.SSH_FXP_SETSTAT, 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({})[{}]: {}", getClientChannel(), 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);
buffer = writeAttributes(SftpConstants.SSH_FXP_FSETSTAT, 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 = putReferencedName(SftpConstants.SSH_FXP_READLINK, buffer, path, 0);
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={}] {} => {}", getClientChannel(), 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 = putReferencedName(SftpConstants.SSH_FXP_SYMLINK, buffer, targetPath, 0);
buffer = putReferencedName(SftpConstants.SSH_FXP_SYMLINK, buffer, linkPath, 1);
checkCommandStatus(SftpConstants.SSH_FXP_SYMLINK, buffer);
} else {
buffer = putReferencedName(SftpConstants.SSH_FXP_SYMLINK, buffer, targetPath, 0);
buffer = putReferencedName(SftpConstants.SSH_FXP_SYMLINK, buffer, linkPath, 1);
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{}",
getClientChannel(), 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={}", getClientChannel(), 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);
}
@Override
public Iterable readDir(String path) throws IOException {
if (!isOpen()) {
throw new IOException("readDir(" + path + ") client is closed");
}
return new SftpIterableDirEntry(this, path);
}
@Override
public Iterable listDir(Handle handle) throws IOException {
if (!isOpen()) {
throw new IOException("listDir(" + handle + ") client is closed");
}
return new StfpIterableDirHandle(this, handle);
}
@Override
public FileChannel openRemoteFileChannel(String path, Collection modes) throws IOException {
return new SftpRemotePathChannel(path, this, false, GenericUtils.isEmpty(modes) ? DEFAULT_CHANNEL_MODES : modes);
}
@Override
public InputStream read(String path, int bufferSize, Collection mode) throws IOException {
if (bufferSize <= 0) {
bufferSize = getReadBufferSize();
}
if (bufferSize < MIN_WRITE_BUFFER_SIZE) {
throw new IllegalArgumentException(
"Insufficient read buffer size: " + bufferSize + ", min.="
+ MIN_READ_BUFFER_SIZE);
}
if (!isOpen()) {
throw new IOException("write(" + path + ")[" + mode + "] size=" + bufferSize + ": client is closed");
}
return new SftpInputStreamAsync(this, bufferSize, path, mode);
}
@Override
public InputStream read(String path, Collection mode) throws IOException {
int packetSize = (int) getChannel().getRemoteWindow().getPacketSize();
return read(path, packetSize, mode);
}
@Override
public OutputStream write(String path, int bufferSize, Collection mode) throws IOException {
if (bufferSize <= 0) {
bufferSize = getWriteBufferSize();
}
if (bufferSize < MIN_WRITE_BUFFER_SIZE) {
throw new IllegalArgumentException(
"Insufficient write buffer size: " + bufferSize + ", min.="
+ MIN_WRITE_BUFFER_SIZE);
}
if (!isOpen()) {
throw new IOException("write(" + path + ")[" + mode + "] size=" + bufferSize + ": client is closed");
}
return new SftpOutputStreamAsync(this, bufferSize, path, mode);
}
@Override
public OutputStream write(String path, Collection mode) throws IOException {
int packetSize = (int) getChannel().getRemoteWindow().getPacketSize();
return write(path, packetSize, mode);
}
protected int getReadBufferSize() {
return (int) getClientChannel().getLocalWindow().getPacketSize() - 13;
}
protected int getWriteBufferSize() {
return (int) getClientChannel().getLocalWindow().getPacketSize() - 13;
}
}