blob: 4bff81d549ceb3f5205e5c48a6817c5022364032 [file] [log] [blame]
/*
* 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.extensions.helpers;
import java.io.IOException;
import java.io.StreamCorruptedException;
import java.time.Duration;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import org.apache.sshd.common.SshException;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.MapEntryUtils;
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.common.util.logging.AbstractLoggingBean;
import org.apache.sshd.sftp.client.RawSftpClient;
import org.apache.sshd.sftp.client.SftpClient;
import org.apache.sshd.sftp.client.SftpClient.Handle;
import org.apache.sshd.sftp.client.extensions.SftpClientExtension;
import org.apache.sshd.sftp.common.SftpConstants;
import org.apache.sshd.sftp.common.SftpException;
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public abstract class AbstractSftpClientExtension extends AbstractLoggingBean implements SftpClientExtension, RawSftpClient {
private final String name;
private final SftpClient client;
private final RawSftpClient raw;
private final boolean supported;
protected AbstractSftpClientExtension(String name, SftpClient client, RawSftpClient raw, Collection<String> extras) {
this(name, client, raw, GenericUtils.isNotEmpty(extras) && extras.contains(name));
}
protected AbstractSftpClientExtension(String name, SftpClient client, RawSftpClient raw, Map<String, byte[]> extensions) {
this(name, client, raw, MapEntryUtils.isNotEmpty(extensions) && extensions.containsKey(name));
}
protected AbstractSftpClientExtension(String name, SftpClient client, RawSftpClient raw, boolean supported) {
this.name = ValidateUtils.checkNotNullAndNotEmpty(name, "No extension name");
this.client = Objects.requireNonNull(client, "No client instance");
this.raw = Objects.requireNonNull(raw, "No raw access");
this.supported = supported;
}
@Override
public final String getName() {
return name;
}
@Override
public final SftpClient getClient() {
return client;
}
protected void sendAndCheckExtendedCommandStatus(Buffer buffer) throws IOException {
int reqId = sendExtendedCommand(buffer);
if (log.isDebugEnabled()) {
log.debug("sendAndCheckExtendedCommandStatus({}) id={}", getName(), reqId);
}
checkStatus(receive(reqId));
}
protected int sendExtendedCommand(Buffer buffer) throws IOException {
return send(SftpConstants.SSH_FXP_EXTENDED, buffer);
}
@Override
public int send(int cmd, Buffer buffer) throws IOException {
return raw.send(cmd, buffer);
}
@Override
public Buffer receive(int id) throws IOException {
return raw.receive(id);
}
@Override
public Buffer receive(int id, long timeout) throws IOException {
return raw.receive(id, timeout);
}
@Override
public Buffer receive(int id, Duration timeout) throws IOException {
return raw.receive(id, timeout);
}
@Override
public final boolean isSupported() {
return supported;
}
protected void checkStatus(Buffer buffer) throws IOException {
if (checkExtendedReplyBuffer(buffer) != null) {
throw new StreamCorruptedException("Unexpected extended reply received");
}
}
/**
* @param buffer The {@link Buffer}
* @param target A target path {@link String} or {@link Handle} or {@code byte[]} to be
* encoded in the buffer
* @return The updated buffer
* @throws UnsupportedOperationException If target is not one of the above supported types
*/
public Buffer putTarget(Buffer buffer, Object target) {
if (target instanceof CharSequence) {
buffer.putString(target.toString());
} else if (target instanceof byte[]) {
buffer.putBytes((byte[]) target);
} else if (target instanceof Handle) {
buffer.putBytes(((Handle) target).getIdentifier());
} else {
throw new UnsupportedOperationException("Unknown target type: " + target);
}
return buffer;
}
/**
* @param target A target path {@link String} or {@link Handle} or {@code byte[]} to be encoded in the buffer
* @return A {@link Buffer} with the extension name set
* @see #getCommandBuffer(Object, int)
*/
protected Buffer getCommandBuffer(Object target) {
return getCommandBuffer(target, 0);
}
/**
* @param target A target path {@link String} or {@link Handle} or {@code byte[]} to be encoded in the buffer
* @param extraSize Extra size - beyond the path/handle to be allocated
* @return A {@link Buffer} with the extension name set
* @see #getCommandBuffer(int)
*/
protected Buffer getCommandBuffer(Object target, int extraSize) {
if (target instanceof CharSequence) {
return getCommandBuffer(Integer.BYTES + ((CharSequence) target).length() + extraSize);
} else if (target instanceof byte[]) {
return getCommandBuffer(Integer.BYTES + ((byte[]) target).length + extraSize);
} else if (target instanceof Handle) {
return getCommandBuffer(Integer.BYTES + ((Handle) target).length() + extraSize);
} else {
return getCommandBuffer(extraSize);
}
}
/**
* @param extraSize Extra size - besides the extension name
* @return A {@link Buffer} with the extension name set
*/
protected Buffer getCommandBuffer(int extraSize) {
String opcode = getName();
Buffer buffer = new ByteArrayBuffer(Integer.BYTES + GenericUtils.length(opcode) + extraSize + Byte.SIZE, false);
buffer.putString(opcode);
return buffer;
}
/**
* @param buffer The {@link Buffer} to check
* @return The {@link Buffer} if this is an {@link SftpConstants#SSH_FXP_EXTENDED_REPLY}, or
* {@code null} if this is a {@link SftpConstants#SSH_FXP_STATUS} carrying an
* {@link SftpConstants#SSH_FX_OK} result
* @throws IOException If a non-{@link SftpConstants#SSH_FX_OK} result or not a
* {@link SftpConstants#SSH_FXP_EXTENDED_REPLY} buffer
*/
protected Buffer checkExtendedReplyBuffer(Buffer buffer) throws IOException {
int length = buffer.getInt();
int type = buffer.getUByte();
int id = buffer.getInt();
validateIncomingResponse(SftpConstants.SSH_FXP_EXTENDED, id, type, length, buffer);
if (type == SftpConstants.SSH_FXP_STATUS) {
int substatus = buffer.getInt();
String msg = buffer.getString();
String lang = buffer.getString();
if (log.isDebugEnabled()) {
log.debug("checkExtendedReplyBuffer({})[id={}] - status: {} [{}] {}",
getName(), id, substatus, lang, msg);
}
if (substatus != SftpConstants.SSH_FX_OK) {
throwStatusException(id, substatus, msg, lang);
}
return null;
} else if (type == SftpConstants.SSH_FXP_EXTENDED_REPLY) {
return buffer;
} else {
throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
}
}
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 void throwStatusException(int id, int substatus, String msg, String lang) throws IOException {
throw new SftpException(substatus, msg);
}
}