/*
* 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.extensions.helpers;
import java.io.IOException;
import java.io.StreamCorruptedException;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import org.apache.sshd.client.subsystem.sftp.RawSftpClient;
import org.apache.sshd.client.subsystem.sftp.SftpClient;
import org.apache.sshd.client.subsystem.sftp.SftpClient.Handle;
import org.apache.sshd.client.subsystem.sftp.extensions.SftpClientExtension;
import org.apache.sshd.common.SshException;
import org.apache.sshd.common.subsystem.sftp.SftpConstants;
import org.apache.sshd.common.subsystem.sftp.SftpException;
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.common.util.logging.AbstractLoggingBean;
/**
* @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, GenericUtils.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(" + getName() + ") id=" + 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 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();
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 throwStatusException(int id, int substatus, String msg, String lang) throws IOException {
throw new SftpException(substatus, msg);
}
}