http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/AbstractSftpSubsystemHelper.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/AbstractSftpSubsystemHelper.java b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/AbstractSftpSubsystemHelper.java deleted file mode 100644 index 08213ee..0000000 --- a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/AbstractSftpSubsystemHelper.java +++ /dev/null @@ -1,2580 +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.server.subsystem.sftp; - -import java.io.EOFException; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.StreamCorruptedException; -import java.nio.ByteBuffer; -import java.nio.channels.SeekableByteChannel; -import java.nio.charset.StandardCharsets; -import java.nio.file.AccessDeniedException; -import java.nio.file.CopyOption; -import java.nio.file.FileAlreadyExistsException; -import java.nio.file.FileStore; -import java.nio.file.FileSystem; -import java.nio.file.Files; -import java.nio.file.InvalidPathException; -import java.nio.file.LinkOption; -import java.nio.file.NoSuchFileException; -import java.nio.file.NotDirectoryException; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.nio.file.StandardOpenOption; -import java.nio.file.attribute.AclEntry; -import java.nio.file.attribute.AclFileAttributeView; -import java.nio.file.attribute.FileOwnerAttributeView; -import java.nio.file.attribute.FileTime; -import java.nio.file.attribute.GroupPrincipal; -import java.nio.file.attribute.PosixFileAttributeView; -import java.nio.file.attribute.PosixFilePermission; -import java.nio.file.attribute.UserPrincipal; -import java.nio.file.attribute.UserPrincipalLookupService; -import java.nio.file.attribute.UserPrincipalNotFoundException; -import java.security.Principal; -import java.util.AbstractMap.SimpleImmutableEntry; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.EnumSet; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.NavigableMap; -import java.util.Objects; -import java.util.Set; -import java.util.TreeMap; -import java.util.TreeSet; -import java.util.concurrent.CopyOnWriteArraySet; -import java.util.function.IntUnaryOperator; - -import org.apache.sshd.common.FactoryManager; -import org.apache.sshd.common.NamedFactory; -import org.apache.sshd.common.NamedResource; -import org.apache.sshd.common.OptionalFeature; -import org.apache.sshd.common.PropertyResolver; -import org.apache.sshd.common.PropertyResolverUtils; -import org.apache.sshd.common.config.VersionProperties; -import org.apache.sshd.common.digest.BuiltinDigests; -import org.apache.sshd.common.digest.Digest; -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.extensions.AclSupportedParser; -import org.apache.sshd.common.subsystem.sftp.extensions.SpaceAvailableExtensionInfo; -import org.apache.sshd.common.subsystem.sftp.extensions.openssh.AbstractOpenSSHExtensionParser.OpenSSHExtension; -import org.apache.sshd.common.subsystem.sftp.extensions.openssh.FsyncExtensionParser; -import org.apache.sshd.common.subsystem.sftp.extensions.openssh.HardLinkExtensionParser; -import org.apache.sshd.common.util.EventListenerUtils; -import org.apache.sshd.common.util.GenericUtils; -import org.apache.sshd.common.util.NumberUtils; -import org.apache.sshd.common.util.OsUtils; -import org.apache.sshd.common.util.SelectorUtils; -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.io.FileInfoExtractor; -import org.apache.sshd.common.util.io.IoUtils; -import org.apache.sshd.common.util.logging.AbstractLoggingBean; -import org.apache.sshd.server.session.ServerSession; - -/** - * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> - */ -public abstract class AbstractSftpSubsystemHelper - extends AbstractLoggingBean - implements SftpEventListenerManager, SftpSubsystemEnvironment { - /** - * Whether to automatically follow symbolic links when resolving paths - * @see #DEFAULT_AUTO_FOLLOW_LINKS - */ - public static final String AUTO_FOLLOW_LINKS = "sftp-auto-follow-links"; - - /** - * Default value of {@value #AUTO_FOLLOW_LINKS} - */ - public static final boolean DEFAULT_AUTO_FOLLOW_LINKS = true; - - /** - * Allows controlling reports of which client extensions are supported - * (and reported via "support" and "support2" server - * extensions) as a comma-separate list of names. <B>Note:</B> requires - * overriding the {@link #executeExtendedCommand(Buffer, int, String)} - * command accordingly. If empty string is set then no server extensions - * are reported - * - * @see #DEFAULT_SUPPORTED_CLIENT_EXTENSIONS - */ - public static final String CLIENT_EXTENSIONS_PROP = "sftp-client-extensions"; - - /** - * The default reported supported client extensions - */ - public static final Map<String, OptionalFeature> DEFAULT_SUPPORTED_CLIENT_EXTENSIONS = - // TODO text-seek - see http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/draft-ietf-secsh-filexfer-13.txt - // TODO home-directory - see http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/draft-ietf-secsh-filexfer-09.txt - GenericUtils.<String, OptionalFeature>mapBuilder() - .put(SftpConstants.EXT_VERSION_SELECT, OptionalFeature.TRUE) - .put(SftpConstants.EXT_COPY_FILE, OptionalFeature.TRUE) - .put(SftpConstants.EXT_MD5_HASH, BuiltinDigests.md5) - .put(SftpConstants.EXT_MD5_HASH_HANDLE, BuiltinDigests.md5) - .put(SftpConstants.EXT_CHECK_FILE_HANDLE, OptionalFeature.any(BuiltinDigests.VALUES)) - .put(SftpConstants.EXT_CHECK_FILE_NAME, OptionalFeature.any(BuiltinDigests.VALUES)) - .put(SftpConstants.EXT_COPY_DATA, OptionalFeature.TRUE) - .put(SftpConstants.EXT_SPACE_AVAILABLE, OptionalFeature.TRUE) - .immutable(); - - /** - * Comma-separated list of which {@code OpenSSH} extensions are reported and - * what version is reported for each - format: {@code name=version}. If empty - * value set, then no such extensions are reported. Otherwise, the - * {@link #DEFAULT_OPEN_SSH_EXTENSIONS} are used - */ - public static final String OPENSSH_EXTENSIONS_PROP = "sftp-openssh-extensions"; - public static final List<OpenSSHExtension> DEFAULT_OPEN_SSH_EXTENSIONS = - Collections.unmodifiableList( - Arrays.asList( - new OpenSSHExtension(FsyncExtensionParser.NAME, "1"), - new OpenSSHExtension(HardLinkExtensionParser.NAME, "1") - )); - - public static final List<String> DEFAULT_OPEN_SSH_EXTENSIONS_NAMES = - Collections.unmodifiableList(NamedResource.getNameList(DEFAULT_OPEN_SSH_EXTENSIONS)); - - /** - * Comma separate list of {@code SSH_ACL_CAP_xxx} names - where name can be without - * the prefix. If not defined then {@link #DEFAULT_ACL_SUPPORTED_MASK} is used - */ - public static final String ACL_SUPPORTED_MASK_PROP = "sftp-acl-supported-mask"; - public static final Set<Integer> DEFAULT_ACL_SUPPORTED_MASK = - Collections.unmodifiableSet( - new HashSet<>(Arrays.asList( - SftpConstants.SSH_ACL_CAP_ALLOW, - SftpConstants.SSH_ACL_CAP_DENY, - SftpConstants.SSH_ACL_CAP_AUDIT, - SftpConstants.SSH_ACL_CAP_ALARM))); - - /** - * Property that can be used to set the reported NL value. - * If not set, then {@link IoUtils#EOL} is used - */ - public static final String NEWLINE_VALUE = "sftp-newline"; - - /** - * Force the use of a max. packet length for {@link #doRead(Buffer, int)} protection - * against malicious packets - * - * @see #DEFAULT_MAX_READDATA_PACKET_LENGTH - */ - public static final String MAX_READDATA_PACKET_LENGTH_PROP = "sftp-max-readdata-packet-length"; - public static final int DEFAULT_MAX_READDATA_PACKET_LENGTH = 63 * 1024; - - private final UnsupportedAttributePolicy unsupportedAttributePolicy; - private final Collection<SftpEventListener> sftpEventListeners = new CopyOnWriteArraySet<>(); - private final SftpEventListener sftpEventListenerProxy; - private final SftpFileSystemAccessor fileSystemAccessor; - private final SftpErrorStatusDataHandler errorStatusDataHandler; - - protected AbstractSftpSubsystemHelper( - UnsupportedAttributePolicy policy, SftpFileSystemAccessor accessor, SftpErrorStatusDataHandler handler) { - unsupportedAttributePolicy = Objects.requireNonNull(policy, "No unsupported attribute policy provided"); - fileSystemAccessor = Objects.requireNonNull(accessor, "No file system accessor"); - sftpEventListenerProxy = EventListenerUtils.proxyWrapper(SftpEventListener.class, getClass().getClassLoader(), sftpEventListeners); - errorStatusDataHandler = Objects.requireNonNull(handler, "No error status data handler"); - } - - @Override - public UnsupportedAttributePolicy getUnsupportedAttributePolicy() { - return unsupportedAttributePolicy; - } - - @Override - public SftpFileSystemAccessor getFileSystemAccessor() { - return fileSystemAccessor; - } - - @Override - public SftpEventListener getSftpEventListenerProxy() { - return sftpEventListenerProxy; - } - - @Override - public boolean addSftpEventListener(SftpEventListener listener) { - return sftpEventListeners.add(SftpEventListener.validateListener(listener)); - } - - @Override - public boolean removeSftpEventListener(SftpEventListener listener) { - if (listener == null) { - return false; - } - - return sftpEventListeners.remove(SftpEventListener.validateListener(listener)); - } - - public SftpErrorStatusDataHandler getErrorStatusDataHandler() { - return errorStatusDataHandler; - } - - protected abstract void process(Buffer buffer) throws IOException; - - /** - * @param buffer The {@link Buffer} holding the request - * @param id The request id - * @param proposed The proposed value - * @return A {@link Boolean} indicating whether to accept/reject the proposal. - * If {@code null} then rejection response has been sent, otherwise and - * appropriate response is generated - * @throws IOException If failed send an independent rejection response - */ - protected Boolean validateProposedVersion(Buffer buffer, int id, String proposed) throws IOException { - if (log.isDebugEnabled()) { - log.debug("validateProposedVersion({})[id={}] SSH_FXP_EXTENDED(version-select) (version={})", - getServerSession(), id, proposed); - } - - if (GenericUtils.length(proposed) != 1) { - return Boolean.FALSE; - } - - char digit = proposed.charAt(0); - if ((digit < '0') || (digit > '9')) { - return Boolean.FALSE; - } - - int value = digit - '0'; - String all = checkVersionCompatibility(buffer, id, value, SftpConstants.SSH_FX_FAILURE); - if (GenericUtils.isEmpty(all)) { // validation failed - return null; - } else { - return Boolean.TRUE; - } - } - - /** - * Checks if a proposed version is within supported range. <B>Note:</B> - * if the user forced a specific value via the {@link SftpSubsystemEnvironment#SFTP_VERSION} - * property, then it is used to validate the proposed value - * - * @param buffer The {@link Buffer} containing the request - * @param id The SSH message ID to be used to send the failure message - * if required - * @param proposed The proposed version value - * @param failureOpcode The failure opcode to send if validation fails - * @return A {@link String} of comma separated values representing all - * the supported version - {@code null} if validation failed and an - * appropriate status message was sent - * @throws IOException If failed to send the failure status message - */ - protected String checkVersionCompatibility(Buffer buffer, int id, int proposed, int failureOpcode) throws IOException { - int low = SftpSubsystemEnvironment.LOWER_SFTP_IMPL; - int hig = SftpSubsystemEnvironment.HIGHER_SFTP_IMPL; - String available = SftpSubsystemEnvironment.ALL_SFTP_IMPL; - // check if user wants to use a specific version - ServerSession session = getServerSession(); - Integer sftpVersion = session.getInteger(SftpSubsystemEnvironment.SFTP_VERSION); - if (sftpVersion != null) { - int forcedValue = sftpVersion; - if ((forcedValue < SftpSubsystemEnvironment.LOWER_SFTP_IMPL) || (forcedValue > SftpSubsystemEnvironment.HIGHER_SFTP_IMPL)) { - throw new IllegalStateException("Forced SFTP version (" + sftpVersion + ") not within supported values: " + available); - } - hig = sftpVersion; - low = hig; - available = sftpVersion.toString(); - } - - if (log.isTraceEnabled()) { - log.trace("checkVersionCompatibility({})[id={}] - proposed={}, available={}", - getServerSession(), id, proposed, available); - } - - if ((proposed < low) || (proposed > hig)) { - sendStatus(BufferUtils.clear(buffer), id, failureOpcode, "Proposed version (" + proposed + ") not in supported range: " + available); - return null; - } - - return available; - } - - protected void doOpen(Buffer buffer, int id) throws IOException { - String path = buffer.getString(); - /* - * Be consistent with FileChannel#open - if no mode specified then READ is assumed - */ - int access = 0; - int version = getVersion(); - if (version >= SftpConstants.SFTP_V5) { - access = buffer.getInt(); - if (access == 0) { - access = SftpConstants.ACE4_READ_DATA | SftpConstants.ACE4_READ_ATTRIBUTES; - } - } - - int pflags = buffer.getInt(); - if (pflags == 0) { - pflags = SftpConstants.SSH_FXF_READ; - } - - if (version < SftpConstants.SFTP_V5) { - int flags = pflags; - pflags = 0; - switch (flags & (SftpConstants.SSH_FXF_READ | SftpConstants.SSH_FXF_WRITE)) { - case SftpConstants.SSH_FXF_READ: - access |= SftpConstants.ACE4_READ_DATA | SftpConstants.ACE4_READ_ATTRIBUTES; - break; - case SftpConstants.SSH_FXF_WRITE: - access |= SftpConstants.ACE4_WRITE_DATA | SftpConstants.ACE4_WRITE_ATTRIBUTES; - break; - default: - access |= SftpConstants.ACE4_READ_DATA | SftpConstants.ACE4_READ_ATTRIBUTES; - access |= SftpConstants.ACE4_WRITE_DATA | SftpConstants.ACE4_WRITE_ATTRIBUTES; - break; - } - if ((flags & SftpConstants.SSH_FXF_APPEND) != 0) { - access |= SftpConstants.ACE4_APPEND_DATA; - pflags |= SftpConstants.SSH_FXF_APPEND_DATA | SftpConstants.SSH_FXF_APPEND_DATA_ATOMIC; - } - if ((flags & SftpConstants.SSH_FXF_CREAT) != 0) { - if ((flags & SftpConstants.SSH_FXF_EXCL) != 0) { - pflags |= SftpConstants.SSH_FXF_CREATE_NEW; - } else if ((flags & SftpConstants.SSH_FXF_TRUNC) != 0) { - pflags |= SftpConstants.SSH_FXF_CREATE_TRUNCATE; - } else { - pflags |= SftpConstants.SSH_FXF_OPEN_OR_CREATE; - } - } else { - if ((flags & SftpConstants.SSH_FXF_TRUNC) != 0) { - pflags |= SftpConstants.SSH_FXF_TRUNCATE_EXISTING; - } else { - pflags |= SftpConstants.SSH_FXF_OPEN_EXISTING; - } - } - } - - Map<String, Object> attrs = readAttrs(buffer); - String handle; - try { - handle = doOpen(id, path, pflags, access, attrs); - } catch (IOException | RuntimeException e) { - sendStatus(BufferUtils.clear(buffer), id, e, SftpConstants.SSH_FXP_OPEN, path); - return; - } - - sendHandle(BufferUtils.clear(buffer), id, handle); - } - - /** - * @param id Request id - * @param path Path - * @param pflags Open mode flags - see {@code SSH_FXF_XXX} flags - * @param access Access mode flags - see {@code ACE4_XXX} flags - * @param attrs Requested attributes - * @return The assigned (opaque) handle - * @throws IOException if failed to execute - */ - protected abstract String doOpen(int id, String path, int pflags, int access, Map<String, Object> attrs) throws IOException; - - protected void doClose(Buffer buffer, int id) throws IOException { - String handle = buffer.getString(); - try { - doClose(id, handle); - } catch (IOException | RuntimeException e) { - sendStatus(BufferUtils.clear(buffer), id, e, SftpConstants.SSH_FXP_CLOSE, handle); - return; - } - - sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, "", ""); - } - - protected abstract void doClose(int id, String handle) throws IOException; - - protected void doRead(Buffer buffer, int id) throws IOException { - String handle = buffer.getString(); - long offset = buffer.getLong(); - int requestedLength = buffer.getInt(); - ServerSession serverSession = getServerSession(); - int maxAllowed = serverSession.getIntProperty(MAX_READDATA_PACKET_LENGTH_PROP, DEFAULT_MAX_READDATA_PACKET_LENGTH); - int readLen = Math.min(requestedLength, maxAllowed); - if (log.isTraceEnabled()) { - log.trace("doRead({})[id={}]({})[offset={}] - req={}, max={}, effective={}", - serverSession, id, handle, offset, requestedLength, maxAllowed, readLen); - } - - try { - ValidateUtils.checkTrue(readLen >= 0, "Illegal requested read length: %d", readLen); - - buffer.clear(); - buffer.ensureCapacity(readLen + Long.SIZE /* the header */, IntUnaryOperator.identity()); - - buffer.putByte((byte) SftpConstants.SSH_FXP_DATA); - buffer.putInt(id); - int lenPos = buffer.wpos(); - buffer.putInt(0); - - int startPos = buffer.wpos(); - int len = doRead(id, handle, offset, readLen, buffer.array(), startPos); - if (len < 0) { - throw new EOFException("Unable to read " + readLen + " bytes from offset=" + offset + " of " + handle); - } - buffer.wpos(startPos + len); - BufferUtils.updateLengthPlaceholder(buffer, lenPos, len); - } catch (IOException | RuntimeException e) { - sendStatus(BufferUtils.clear(buffer), id, e, SftpConstants.SSH_FXP_READ, handle, offset, requestedLength); - return; - } - - send(buffer); - } - - protected abstract int doRead(int id, String handle, long offset, int length, byte[] data, int doff) throws IOException; - - protected void doWrite(Buffer buffer, int id) throws IOException { - String handle = buffer.getString(); - long offset = buffer.getLong(); - int length = buffer.getInt(); - try { - doWrite(id, handle, offset, length, buffer.array(), buffer.rpos(), buffer.available()); - } catch (IOException | RuntimeException e) { - sendStatus(BufferUtils.clear(buffer), id, e, SftpConstants.SSH_FXP_WRITE, handle, offset, length); - return; - } - - sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, ""); - } - - protected abstract void doWrite(int id, String handle, long offset, int length, byte[] data, int doff, int remaining) throws IOException; - - protected void doLStat(Buffer buffer, int id) throws IOException { - String path = buffer.getString(); - int flags = SftpConstants.SSH_FILEXFER_ATTR_ALL; - int version = getVersion(); - if (version >= SftpConstants.SFTP_V4) { - flags = buffer.getInt(); - } - - Map<String, ?> attrs; - try { - attrs = doLStat(id, path, flags); - } catch (IOException | RuntimeException e) { - sendStatus(BufferUtils.clear(buffer), id, e, SftpConstants.SSH_FXP_LSTAT, path, flags); - return; - } - - sendAttrs(BufferUtils.clear(buffer), id, attrs); - } - - protected Map<String, Object> doLStat(int id, String path, int flags) throws IOException { - Path p = resolveFile(path); - if (log.isDebugEnabled()) { - log.debug("doLStat({})[id={}] SSH_FXP_LSTAT (path={}[{}], flags=0x{})", - getServerSession(), id, path, p, Integer.toHexString(flags)); - } - - /* - * SSH_FXP_STAT and SSH_FXP_LSTAT only differ in that SSH_FXP_STAT - * follows symbolic links on the server, whereas SSH_FXP_LSTAT does not. - */ - return resolveFileAttributes(p, flags, IoUtils.getLinkOptions(false)); - } - - protected void doSetStat(Buffer buffer, int id) throws IOException { - String path = buffer.getString(); - Map<String, Object> attrs = readAttrs(buffer); - try { - doSetStat(id, path, attrs); - } catch (IOException | RuntimeException e) { - sendStatus(BufferUtils.clear(buffer), id, e, SftpConstants.SSH_FXP_SETSTAT, path); - return; - } - - sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, ""); - } - - protected void doSetStat(int id, String path, Map<String, ?> attrs) throws IOException { - if (log.isDebugEnabled()) { - log.debug("doSetStat({})[id={}] SSH_FXP_SETSTAT (path={}, attrs={})", - getServerSession(), id, path, attrs); - } - Path p = resolveFile(path); - doSetAttributes(p, attrs); - } - - protected void doFStat(Buffer buffer, int id) throws IOException { - String handle = buffer.getString(); - int flags = SftpConstants.SSH_FILEXFER_ATTR_ALL; - int version = getVersion(); - if (version >= SftpConstants.SFTP_V4) { - flags = buffer.getInt(); - } - - Map<String, ?> attrs; - try { - attrs = doFStat(id, handle, flags); - } catch (IOException | RuntimeException e) { - sendStatus(BufferUtils.clear(buffer), id, e, SftpConstants.SSH_FXP_FSTAT, handle, flags); - return; - } - - sendAttrs(BufferUtils.clear(buffer), id, attrs); - } - - protected abstract Map<String, Object> doFStat(int id, String handle, int flags) throws IOException; - - protected void doFSetStat(Buffer buffer, int id) throws IOException { - String handle = buffer.getString(); - Map<String, Object> attrs = readAttrs(buffer); - try { - doFSetStat(id, handle, attrs); - } catch (IOException | RuntimeException e) { - sendStatus(BufferUtils.clear(buffer), id, e, SftpConstants.SSH_FXP_FSETSTAT, handle, attrs); - return; - } - - sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, ""); - } - - protected abstract void doFSetStat(int id, String handle, Map<String, ?> attrs) throws IOException; - - protected void doOpenDir(Buffer buffer, int id) throws IOException { - String path = buffer.getString(); - String handle; - - try { - Path p = resolveNormalizedLocation(path); - if (log.isDebugEnabled()) { - log.debug("doOpenDir({})[id={}] SSH_FXP_OPENDIR (path={})[{}]", - getServerSession(), id, path, p); - } - - LinkOption[] options = - getPathResolutionLinkOption(SftpConstants.SSH_FXP_OPENDIR, "", p); - handle = doOpenDir(id, path, p, options); - } catch (IOException | RuntimeException e) { - sendStatus(BufferUtils.clear(buffer), id, e, SftpConstants.SSH_FXP_OPENDIR, path); - return; - } - - sendHandle(BufferUtils.clear(buffer), id, handle); - } - - protected abstract String doOpenDir(int id, String path, Path p, LinkOption... options) throws IOException; - - protected abstract void doReadDir(Buffer buffer, int id) throws IOException; - - protected void doLink(Buffer buffer, int id) throws IOException { - String targetPath = buffer.getString(); - String linkPath = buffer.getString(); - boolean symLink = buffer.getBoolean(); - - try { - if (log.isDebugEnabled()) { - log.debug("doLink({})[id={}] SSH_FXP_LINK linkpath={}, targetpath={}, symlink={}", - getServerSession(), id, linkPath, targetPath, symLink); - } - - doLink(id, targetPath, linkPath, symLink); - } catch (IOException | RuntimeException e) { - sendStatus(BufferUtils.clear(buffer), id, e, SftpConstants.SSH_FXP_LINK, targetPath, linkPath, symLink); - return; - } - - sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, ""); - } - - protected void doLink(int id, String targetPath, String linkPath, boolean symLink) throws IOException { - createLink(id, targetPath, linkPath, symLink); - } - - protected void doSymLink(Buffer buffer, int id) throws IOException { - String targetPath = buffer.getString(); - String linkPath = buffer.getString(); - try { - if (log.isDebugEnabled()) { - log.debug("doSymLink({})[id={}] SSH_FXP_SYMLINK linkpath={}, targetpath={}", - getServerSession(), id, targetPath, linkPath); - } - doSymLink(id, targetPath, linkPath); - } catch (IOException | RuntimeException e) { - sendStatus(BufferUtils.clear(buffer), id, e, SftpConstants.SSH_FXP_SYMLINK, targetPath, linkPath); - return; - } - - sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, ""); - } - - protected void doSymLink(int id, String targetPath, String linkPath) throws IOException { - createLink(id, targetPath, linkPath, true); - } - - protected abstract void createLink(int id, String existingPath, String linkPath, boolean symLink) throws IOException; - - // see https://github.com/openssh/openssh-portable/blob/master/PROTOCOL section 10 - protected void doOpenSSHHardLink(Buffer buffer, int id) throws IOException { - String srcFile = buffer.getString(); - String dstFile = buffer.getString(); - - try { - doOpenSSHHardLink(id, srcFile, dstFile); - } catch (IOException | RuntimeException e) { - sendStatus(BufferUtils.clear(buffer), id, e, SftpConstants.SSH_FXP_EXTENDED, HardLinkExtensionParser.NAME, srcFile, dstFile); - return; - } - - sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, ""); - } - - protected void doOpenSSHHardLink(int id, String srcFile, String dstFile) throws IOException { - if (log.isDebugEnabled()) { - log.debug("doOpenSSHHardLink({})[id={}] SSH_FXP_EXTENDED[{}] (src={}, dst={})", - getServerSession(), id, HardLinkExtensionParser.NAME, srcFile, dstFile); - } - - createLink(id, srcFile, dstFile, false); - } - - protected void doSpaceAvailable(Buffer buffer, int id) throws IOException { - String path = buffer.getString(); - SpaceAvailableExtensionInfo info; - try { - info = doSpaceAvailable(id, path); - } catch (IOException | RuntimeException e) { - sendStatus(BufferUtils.clear(buffer), id, e, SftpConstants.SSH_FXP_EXTENDED, SftpConstants.EXT_SPACE_AVAILABLE, path); - return; - } - - buffer.clear(); - buffer.putByte((byte) SftpConstants.SSH_FXP_EXTENDED_REPLY); - buffer.putInt(id); - SpaceAvailableExtensionInfo.encode(buffer, info); - send(buffer); - } - - protected SpaceAvailableExtensionInfo doSpaceAvailable(int id, String path) throws IOException { - Path nrm = resolveNormalizedLocation(path); - if (log.isDebugEnabled()) { - log.debug("doSpaceAvailable({})[id={}] path={}[{}]", getServerSession(), id, path, nrm); - } - - FileStore store = Files.getFileStore(nrm); - if (log.isTraceEnabled()) { - log.trace("doSpaceAvailable({})[id={}] path={}[{}] - {}[{}]", - getServerSession(), id, path, nrm, store.name(), store.type()); - } - - return new SpaceAvailableExtensionInfo(store); - } - - protected void doTextSeek(Buffer buffer, int id) throws IOException { - String handle = buffer.getString(); - long line = buffer.getLong(); - try { - // TODO : implement text-seek - see https://tools.ietf.org/html/draft-ietf-secsh-filexfer-03#section-6.3 - doTextSeek(id, handle, line); - } catch (IOException | RuntimeException e) { - sendStatus(BufferUtils.clear(buffer), id, e, SftpConstants.SSH_FXP_EXTENDED, SftpConstants.EXT_TEXT_SEEK, handle, line); - return; - } - - sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, ""); - } - - protected abstract void doTextSeek(int id, String handle, long line) throws IOException; - - // see https://github.com/openssh/openssh-portable/blob/master/PROTOCOL section 10 - protected void doOpenSSHFsync(Buffer buffer, int id) throws IOException { - String handle = buffer.getString(); - try { - doOpenSSHFsync(id, handle); - } catch (IOException | RuntimeException e) { - sendStatus(BufferUtils.clear(buffer), id, e, SftpConstants.SSH_FXP_EXTENDED, FsyncExtensionParser.NAME, handle); - return; - } - - sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, ""); - } - - protected abstract void doOpenSSHFsync(int id, String handle) throws IOException; - - protected void doCheckFileHash(Buffer buffer, int id, String targetType) throws IOException { - String target = buffer.getString(); - String algList = buffer.getString(); - String[] algos = GenericUtils.split(algList, ','); - long startOffset = buffer.getLong(); - long length = buffer.getLong(); - int blockSize = buffer.getInt(); - try { - buffer.clear(); - buffer.putByte((byte) SftpConstants.SSH_FXP_EXTENDED_REPLY); - buffer.putInt(id); - buffer.putString(SftpConstants.EXT_CHECK_FILE); - doCheckFileHash(id, targetType, target, Arrays.asList(algos), startOffset, length, blockSize, buffer); - } catch (Exception e) { - sendStatus(BufferUtils.clear(buffer), id, e, - SftpConstants.SSH_FXP_EXTENDED, targetType, target, algList, startOffset, length, blockSize); - return; - } - - send(buffer); - } - - protected void doCheckFileHash(int id, Path file, NamedFactory<? extends Digest> factory, - long startOffset, long length, int blockSize, Buffer buffer) - throws Exception { - ValidateUtils.checkTrue(startOffset >= 0L, "Invalid start offset: %d", startOffset); - ValidateUtils.checkTrue(length >= 0L, "Invalid length: %d", length); - ValidateUtils.checkTrue((blockSize == 0) || (blockSize >= SftpConstants.MIN_CHKFILE_BLOCKSIZE), "Invalid block size: %d", blockSize); - Objects.requireNonNull(factory, "No digest factory provided"); - buffer.putString(factory.getName()); - - long effectiveLength = length; - long totalLength = Files.size(file); - if (effectiveLength == 0L) { - effectiveLength = totalLength - startOffset; - } else { - long maxRead = startOffset + length; - if (maxRead > totalLength) { - effectiveLength = totalLength - startOffset; - } - } - ValidateUtils.checkTrue(effectiveLength > 0L, "Non-positive effective hash data length: %d", effectiveLength); - - byte[] digestBuf = (blockSize == 0) - ? new byte[Math.min((int) effectiveLength, IoUtils.DEFAULT_COPY_SIZE)] - : new byte[Math.min((int) effectiveLength, blockSize)]; - ByteBuffer wb = ByteBuffer.wrap(digestBuf); - SftpFileSystemAccessor accessor = getFileSystemAccessor(); - try (SeekableByteChannel channel = accessor.openFile(getServerSession(), this, file, "", Collections.emptySet())) { - channel.position(startOffset); - - Digest digest = factory.create(); - digest.init(); - - boolean traceEnabled = log.isTraceEnabled(); - if (blockSize == 0) { - while (effectiveLength > 0L) { - int remainLen = Math.min(digestBuf.length, (int) effectiveLength); - ByteBuffer bb = wb; - if (remainLen < digestBuf.length) { - bb = ByteBuffer.wrap(digestBuf, 0, remainLen); - } - bb.clear(); // prepare for next read - - int readLen = channel.read(bb); - if (readLen < 0) { - break; - } - - effectiveLength -= readLen; - digest.update(digestBuf, 0, readLen); - } - - byte[] hashValue = digest.digest(); - if (traceEnabled) { - log.trace("doCheckFileHash({})[{}] offset={}, length={} - algo={}, hash={}", - getServerSession(), file, startOffset, length, - digest.getAlgorithm(), BufferUtils.toHex(':', hashValue)); - } - buffer.putBytes(hashValue); - } else { - for (int count = 0; effectiveLength > 0L; count++) { - int remainLen = Math.min(digestBuf.length, (int) effectiveLength); - ByteBuffer bb = wb; - if (remainLen < digestBuf.length) { - bb = ByteBuffer.wrap(digestBuf, 0, remainLen); - } - bb.clear(); // prepare for next read - - int readLen = channel.read(bb); - if (readLen < 0) { - break; - } - - effectiveLength -= readLen; - digest.update(digestBuf, 0, readLen); - - byte[] hashValue = digest.digest(); // NOTE: this also resets the hash for the next read - if (traceEnabled) { - log.trace("doCheckFileHash({})({})[{}] offset={}, length={} - algo={}, hash={}", - getServerSession(), file, count, startOffset, length, - digest.getAlgorithm(), BufferUtils.toHex(':', hashValue)); - } - buffer.putBytes(hashValue); - } - } - } - } - - protected void doMD5Hash(Buffer buffer, int id, String targetType) throws IOException { - String target = buffer.getString(); - long startOffset = buffer.getLong(); - long length = buffer.getLong(); - byte[] quickCheckHash = buffer.getBytes(); - byte[] hashValue; - - try { - hashValue = doMD5Hash(id, targetType, target, startOffset, length, quickCheckHash); - if (log.isTraceEnabled()) { - log.trace("doMD5Hash({})({})[{}] offset={}, length={}, quick-hash={} - hash={}", - getServerSession(), targetType, target, startOffset, length, - BufferUtils.toHex(':', quickCheckHash), - BufferUtils.toHex(':', hashValue)); - } - - } catch (Exception e) { - sendStatus(BufferUtils.clear(buffer), id, e, - SftpConstants.SSH_FXP_EXTENDED, targetType, target, startOffset, length, quickCheckHash); - return; - } - - buffer.clear(); - buffer.putByte((byte) SftpConstants.SSH_FXP_EXTENDED_REPLY); - buffer.putInt(id); - buffer.putString(targetType); - buffer.putBytes(hashValue); - send(buffer); - } - - protected abstract byte[] doMD5Hash( - int id, String targetType, String target, long startOffset, long length, byte[] quickCheckHash) - throws Exception; - - protected byte[] doMD5Hash(int id, Path path, long startOffset, long length, byte[] quickCheckHash) throws Exception { - ValidateUtils.checkTrue(startOffset >= 0L, "Invalid start offset: %d", startOffset); - ValidateUtils.checkTrue(length > 0L, "Invalid length: %d", length); - if (!BuiltinDigests.md5.isSupported()) { - throw new UnsupportedOperationException(BuiltinDigests.md5.getAlgorithm() + " hash not supported"); - } - - Digest digest = BuiltinDigests.md5.create(); - digest.init(); - - long effectiveLength = length; - byte[] digestBuf = new byte[(int) Math.min(effectiveLength, SftpConstants.MD5_QUICK_HASH_SIZE)]; - ByteBuffer wb = ByteBuffer.wrap(digestBuf); - boolean hashMatches = false; - byte[] hashValue = null; - SftpFileSystemAccessor accessor = getFileSystemAccessor(); - boolean traceEnabled = log.isTraceEnabled(); - try (SeekableByteChannel channel = accessor.openFile(getServerSession(), this, path, null, EnumSet.of(StandardOpenOption.READ))) { - channel.position(startOffset); - - /* - * To quote http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/draft-ietf-secsh-filexfer-09.txt section 9.1.1: - * - * If this is a zero length string, the client does not have the - * data, and is requesting the hash for reasons other than comparing - * with a local file. The server MAY return SSH_FX_OP_UNSUPPORTED in - * this case. - */ - if (NumberUtils.length(quickCheckHash) <= 0) { - // TODO consider limiting it - e.g., if the requested effective length is <= than some (configurable) threshold - hashMatches = true; - } else { - int readLen = channel.read(wb); - if (readLen < 0) { - throw new EOFException("EOF while read initial buffer from " + path); - } - effectiveLength -= readLen; - digest.update(digestBuf, 0, readLen); - - hashValue = digest.digest(); - hashMatches = Arrays.equals(quickCheckHash, hashValue); - if (hashMatches) { - /* - * Need to re-initialize the digester due to the Javadoc: - * - * "The digest method can be called once for a given number - * of updates. After digest has been called, the MessageDigest - * object is reset to its initialized state." - */ - if (effectiveLength > 0L) { - digest = BuiltinDigests.md5.create(); - digest.init(); - digest.update(digestBuf, 0, readLen); - hashValue = null; // start again - } - } else { - if (traceEnabled) { - log.trace("doMD5Hash({})({}) offset={}, length={} - quick-hash mismatched expected={}, actual={}", - getServerSession(), path, startOffset, length, - BufferUtils.toHex(':', quickCheckHash), - BufferUtils.toHex(':', hashValue)); - } - } - } - - if (hashMatches) { - while (effectiveLength > 0L) { - int remainLen = Math.min(digestBuf.length, (int) effectiveLength); - ByteBuffer bb = wb; - if (remainLen < digestBuf.length) { - bb = ByteBuffer.wrap(digestBuf, 0, remainLen); - } - bb.clear(); // prepare for next read - - int readLen = channel.read(bb); - if (readLen < 0) { - break; // user may have specified more than we have available - } - effectiveLength -= readLen; - digest.update(digestBuf, 0, readLen); - } - - if (hashValue == null) { // check if did any more iterations after the quick hash - hashValue = digest.digest(); - } - } else { - hashValue = GenericUtils.EMPTY_BYTE_ARRAY; - } - } - - if (traceEnabled) { - log.trace("doMD5Hash({})({}) offset={}, length={} - matches={}, quick={} hash={}", - getServerSession(), path, startOffset, length, hashMatches, - BufferUtils.toHex(':', quickCheckHash), - BufferUtils.toHex(':', hashValue)); - } - - return hashValue; - } - - protected abstract void doCheckFileHash( - int id, String targetType, String target, Collection<String> algos, - long startOffset, long length, int blockSize, Buffer buffer) - throws Exception; - - protected void doReadLink(Buffer buffer, int id) throws IOException { - String path = buffer.getString(); - String l; - try { - if (log.isDebugEnabled()) { - log.debug("doReadLink({})[id={}] SSH_FXP_READLINK path={}", - getServerSession(), id, path); - } - l = doReadLink(id, path); - } catch (IOException | RuntimeException e) { - sendStatus(BufferUtils.clear(buffer), id, e, SftpConstants.SSH_FXP_READLINK, path); - return; - } - - sendLink(BufferUtils.clear(buffer), id, l); - } - - protected String doReadLink(int id, String path) throws IOException { - Path f = resolveFile(path); - Path t = Files.readSymbolicLink(f); - if (log.isDebugEnabled()) { - log.debug("doReadLink({})[id={}] path={}[{}]: {}", - getServerSession(), id, path, f, t); - } - return t.toString(); - } - - protected void doRename(Buffer buffer, int id) throws IOException { - String oldPath = buffer.getString(); - String newPath = buffer.getString(); - int flags = 0; - int version = getVersion(); - if (version >= SftpConstants.SFTP_V5) { - flags = buffer.getInt(); - } - try { - doRename(id, oldPath, newPath, flags); - } catch (IOException | RuntimeException e) { - sendStatus(BufferUtils.clear(buffer), id, e, SftpConstants.SSH_FXP_RENAME, oldPath, newPath, flags); - return; - } - - sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, ""); - } - - protected void doRename(int id, String oldPath, String newPath, int flags) throws IOException { - if (log.isDebugEnabled()) { - log.debug("doRename({})[id={}] SSH_FXP_RENAME (oldPath={}, newPath={}, flags=0x{})", - getServerSession(), id, oldPath, newPath, Integer.toHexString(flags)); - } - - Collection<CopyOption> opts = Collections.emptyList(); - if (flags != 0) { - opts = new ArrayList<>(); - if ((flags & SftpConstants.SSH_FXP_RENAME_ATOMIC) == SftpConstants.SSH_FXP_RENAME_ATOMIC) { - opts.add(StandardCopyOption.ATOMIC_MOVE); - } - if ((flags & SftpConstants.SSH_FXP_RENAME_OVERWRITE) == SftpConstants.SSH_FXP_RENAME_OVERWRITE) { - opts.add(StandardCopyOption.REPLACE_EXISTING); - } - } - - doRename(id, oldPath, newPath, opts); - } - - protected void doRename(int id, String oldPath, String newPath, Collection<CopyOption> opts) throws IOException { - Path o = resolveFile(oldPath); - Path n = resolveFile(newPath); - SftpEventListener listener = getSftpEventListenerProxy(); - ServerSession session = getServerSession(); - - listener.moving(session, o, n, opts); - try { - Files.move(o, n, GenericUtils.isEmpty(opts) ? IoUtils.EMPTY_COPY_OPTIONS : opts.toArray(new CopyOption[opts.size()])); - } catch (IOException | RuntimeException e) { - listener.moved(session, o, n, opts, e); - throw e; - } - listener.moved(session, o, n, opts, null); - } - - // see https://tools.ietf.org/html/draft-ietf-secsh-filexfer-extensions-00#section-7 - protected void doCopyData(Buffer buffer, int id) throws IOException { - String readHandle = buffer.getString(); - long readOffset = buffer.getLong(); - long readLength = buffer.getLong(); - String writeHandle = buffer.getString(); - long writeOffset = buffer.getLong(); - try { - doCopyData(id, readHandle, readOffset, readLength, writeHandle, writeOffset); - } catch (IOException | RuntimeException e) { - sendStatus(BufferUtils.clear(buffer), id, e, - SftpConstants.SSH_FXP_EXTENDED, SftpConstants.EXT_COPY_DATA, - readHandle, readOffset, readLength, writeHandle, writeOffset); - return; - } - - sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, ""); - } - - protected abstract void doCopyData(int id, String readHandle, long readOffset, long readLength, String writeHandle, long writeOffset) throws IOException; - - // see https://tools.ietf.org/html/draft-ietf-secsh-filexfer-extensions-00#section-6 - protected void doCopyFile(Buffer buffer, int id) throws IOException { - String srcFile = buffer.getString(); - String dstFile = buffer.getString(); - boolean overwriteDestination = buffer.getBoolean(); - - try { - doCopyFile(id, srcFile, dstFile, overwriteDestination); - } catch (IOException | RuntimeException e) { - sendStatus(BufferUtils.clear(buffer), id, e, - SftpConstants.SSH_FXP_EXTENDED, SftpConstants.EXT_COPY_FILE, srcFile, dstFile, overwriteDestination); - return; - } - - sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, ""); - } - - protected void doCopyFile(int id, String srcFile, String dstFile, boolean overwriteDestination) throws IOException { - if (log.isDebugEnabled()) { - log.debug("doCopyFile({})[id={}] SSH_FXP_EXTENDED[{}] (src={}, dst={}, overwrite=0x{})", - getServerSession(), id, SftpConstants.EXT_COPY_FILE, - srcFile, dstFile, overwriteDestination); - } - - doCopyFile(id, srcFile, dstFile, - overwriteDestination - ? Collections.singletonList(StandardCopyOption.REPLACE_EXISTING) - : Collections.emptyList()); - } - - protected void doCopyFile(int id, String srcFile, String dstFile, Collection<CopyOption> opts) throws IOException { - Path src = resolveFile(srcFile); - Path dst = resolveFile(dstFile); - Files.copy(src, dst, GenericUtils.isEmpty(opts) ? IoUtils.EMPTY_COPY_OPTIONS : opts.toArray(new CopyOption[opts.size()])); - } - - protected void doBlock(Buffer buffer, int id) throws IOException { - String handle = buffer.getString(); - long offset = buffer.getLong(); - long length = buffer.getLong(); - int mask = buffer.getInt(); - - try { - doBlock(id, handle, offset, length, mask); - } catch (IOException | RuntimeException e) { - sendStatus(BufferUtils.clear(buffer), id, e, SftpConstants.SSH_FXP_BLOCK, handle, offset, length, mask); - return; - } - - sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, ""); - } - - protected abstract void doBlock(int id, String handle, long offset, long length, int mask) throws IOException; - - protected void doUnblock(Buffer buffer, int id) throws IOException { - String handle = buffer.getString(); - long offset = buffer.getLong(); - long length = buffer.getLong(); - try { - doUnblock(id, handle, offset, length); - } catch (IOException | RuntimeException e) { - sendStatus(BufferUtils.clear(buffer), id, e, SftpConstants.SSH_FXP_UNBLOCK, handle, offset, length); - return; - } - - sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, ""); - } - - protected abstract void doUnblock(int id, String handle, long offset, long length) throws IOException; - - protected void doStat(Buffer buffer, int id) throws IOException { - String path = buffer.getString(); - int flags = SftpConstants.SSH_FILEXFER_ATTR_ALL; - int version = getVersion(); - if (version >= SftpConstants.SFTP_V4) { - flags = buffer.getInt(); - } - - Map<String, Object> attrs; - try { - attrs = doStat(id, path, flags); - } catch (IOException | RuntimeException e) { - sendStatus(BufferUtils.clear(buffer), id, e, SftpConstants.SSH_FXP_STAT, path, flags); - return; - } - - sendAttrs(BufferUtils.clear(buffer), id, attrs); - } - - protected Map<String, Object> doStat(int id, String path, int flags) throws IOException { - if (log.isDebugEnabled()) { - log.debug("doStat({})[id={}] SSH_FXP_STAT (path={}, flags=0x{})", - getServerSession(), id, path, Integer.toHexString(flags)); - } - - /* - * SSH_FXP_STAT and SSH_FXP_LSTAT only differ in that SSH_FXP_STAT - * follows symbolic links on the server, whereas SSH_FXP_LSTAT does not. - */ - Path p = resolveFile(path); - return resolveFileAttributes(p, flags, IoUtils.getLinkOptions(true)); - } - - protected void doRealPath(Buffer buffer, int id) throws IOException { - String path = buffer.getString(); - boolean debugEnabled = log.isDebugEnabled(); - if (debugEnabled) { - log.debug("doRealPath({})[id={}] SSH_FXP_REALPATH (path={})", getServerSession(), id, path); - } - path = GenericUtils.trimToEmpty(path); - if (GenericUtils.isEmpty(path)) { - path = "."; - } - - Map<String, ?> attrs = Collections.emptyMap(); - Map.Entry<Path, Boolean> result; - try { - int version = getVersion(); - if (version < SftpConstants.SFTP_V6) { - /* - * See http://www.openssh.com/txt/draft-ietf-secsh-filexfer-02.txt: - * - * The SSH_FXP_REALPATH request can be used to have the server - * canonicalize any given path name to an absolute path. - * - * See also SSHD-294 - */ - Path p = resolveFile(path); - LinkOption[] options = - getPathResolutionLinkOption(SftpConstants.SSH_FXP_REALPATH, "", p); - result = doRealPathV345(id, path, p, options); - } else { - /* - * See https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.9 - * - * This field is optional, and if it is not present in the packet, it - * is assumed to be SSH_FXP_REALPATH_NO_CHECK. - */ - int control = SftpConstants.SSH_FXP_REALPATH_NO_CHECK; - if (buffer.available() > 0) { - control = buffer.getUByte(); - if (debugEnabled) { - log.debug("doRealPath({}) - control=0x{} for path={}", - getServerSession(), Integer.toHexString(control), path); - } - } - - Collection<String> extraPaths = new LinkedList<>(); - while (buffer.available() > 0) { - extraPaths.add(buffer.getString()); - } - - Path p = resolveFile(path); - LinkOption[] options = - getPathResolutionLinkOption(SftpConstants.SSH_FXP_REALPATH, "", p); - result = doRealPathV6(id, path, extraPaths, p, options); - - p = result.getKey(); - options = getPathResolutionLinkOption(SftpConstants.SSH_FXP_REALPATH, "", p); - Boolean status = result.getValue(); - switch (control) { - case SftpConstants.SSH_FXP_REALPATH_STAT_IF: - if (status == null) { - attrs = handleUnknownStatusFileAttributes(p, SftpConstants.SSH_FILEXFER_ATTR_ALL, options); - } else if (status) { - try { - attrs = getAttributes(p, options); - } catch (IOException e) { - if (debugEnabled) { - log.debug("doRealPath({}) - failed ({}) to retrieve attributes of {}: {}", - getServerSession(), e.getClass().getSimpleName(), p, e.getMessage()); - } - if (log.isTraceEnabled()) { - log.trace("doRealPath(" + getServerSession() + ")[" + p + "] attributes retrieval failure details", e); - } - } - } else { - if (debugEnabled) { - log.debug("doRealPath({}) - dummy attributes for non-existing file: {}", getServerSession(), p); - } - } - break; - case SftpConstants.SSH_FXP_REALPATH_STAT_ALWAYS: - if (status == null) { - attrs = handleUnknownStatusFileAttributes(p, SftpConstants.SSH_FILEXFER_ATTR_ALL, options); - } else if (status) { - attrs = getAttributes(p, options); - } else { - throw new NoSuchFileException(p.toString(), p.toString(), "Real path N/A for target"); - } - break; - case SftpConstants.SSH_FXP_REALPATH_NO_CHECK: - break; - default: - log.warn("doRealPath({}) unknown control value 0x{} for path={}", - getServerSession(), Integer.toHexString(control), p); - } - } - } catch (IOException | RuntimeException e) { - sendStatus(BufferUtils.clear(buffer), id, e, SftpConstants.SSH_FXP_REALPATH, path); - return; - } - - sendPath(BufferUtils.clear(buffer), id, result.getKey(), attrs); - } - - protected SimpleImmutableEntry<Path, Boolean> doRealPathV6( - int id, String path, Collection<String> extraPaths, Path p, LinkOption... options) throws IOException { - int numExtra = GenericUtils.size(extraPaths); - if (numExtra > 0) { - if (log.isDebugEnabled()) { - log.debug("doRealPathV6({})[id={}] path={}, extra={}", - getServerSession(), id, path, extraPaths); - } - StringBuilder sb = new StringBuilder(GenericUtils.length(path) + numExtra * 8); - sb.append(path); - - for (String p2 : extraPaths) { - p = p.resolve(p2); - options = getPathResolutionLinkOption(SftpConstants.SSH_FXP_REALPATH, "", p); - sb.append('/').append(p2); - } - - path = sb.toString(); - } - - return validateRealPath(id, path, p, options); - } - - protected SimpleImmutableEntry<Path, Boolean> doRealPathV345(int id, String path, Path p, LinkOption... options) throws IOException { - return validateRealPath(id, path, p, options); - } - - /** - * @param id The request identifier - * @param path The original path - * @param f The resolve {@link Path} - * @param options The {@link LinkOption}s to use to verify file existence and access - * @return A {@link SimpleImmutableEntry} whose key is the <U>absolute <B>normalized</B></U> - * {@link Path} and value is a {@link Boolean} indicating its status - * @throws IOException If failed to validate the file - * @see IoUtils#checkFileExists(Path, LinkOption...) - */ - protected SimpleImmutableEntry<Path, Boolean> validateRealPath(int id, String path, Path f, LinkOption... options) throws IOException { - Path p = normalize(f); - Boolean status = IoUtils.checkFileExists(p, options); - return new SimpleImmutableEntry<>(p, status); - } - - protected void doRemoveDirectory(Buffer buffer, int id) throws IOException { - String path = buffer.getString(); - try { - doRemoveDirectory(id, path, IoUtils.getLinkOptions(false)); - } catch (IOException | RuntimeException e) { - sendStatus(BufferUtils.clear(buffer), id, e, SftpConstants.SSH_FXP_RMDIR, path); - return; - } - - sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, ""); - } - - protected void doRemoveDirectory(int id, String path, LinkOption... options) throws IOException { - Path p = resolveFile(path); - if (log.isDebugEnabled()) { - log.debug("doRemoveDirectory({})[id={}] SSH_FXP_RMDIR (path={})[{}]", - getServerSession(), id, path, p); - } - if (Files.isDirectory(p, options)) { - doRemove(id, p); - } else { - throw new NotDirectoryException(p.toString()); - } - } - - /** - * Called when need to delete a file / directory - also informs the {@link SftpEventListener} - * - * @param id Deletion request ID - * @param p {@link Path} to delete - * @throws IOException If failed to delete - */ - protected void doRemove(int id, Path p) throws IOException { - SftpEventListener listener = getSftpEventListenerProxy(); - ServerSession session = getServerSession(); - listener.removing(session, p); - try { - Files.delete(p); - } catch (IOException | RuntimeException e) { - listener.removed(session, p, e); - throw e; - } - listener.removed(session, p, null); - } - - protected void doMakeDirectory(Buffer buffer, int id) throws IOException { - String path = buffer.getString(); - Map<String, ?> attrs = readAttrs(buffer); - try { - doMakeDirectory(id, path, attrs, IoUtils.getLinkOptions(false)); - } catch (IOException | RuntimeException e) { - sendStatus(BufferUtils.clear(buffer), id, e, SftpConstants.SSH_FXP_MKDIR, path, attrs); - return; - } - - sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, ""); - } - - protected void doMakeDirectory(int id, String path, Map<String, ?> attrs, LinkOption... options) throws IOException { - Path p = resolveFile(path); - if (log.isDebugEnabled()) { - log.debug("doMakeDirectory({})[id={}] SSH_FXP_MKDIR (path={}[{}], attrs={})", - getServerSession(), id, path, p, attrs); - } - - Boolean status = IoUtils.checkFileExists(p, options); - if (status == null) { - throw new AccessDeniedException(p.toString(), p.toString(), "Cannot validate make-directory existence"); - } - - if (status) { - if (Files.isDirectory(p, options)) { - throw new FileAlreadyExistsException(p.toString(), p.toString(), "Target directory already exists"); - } else { - throw new FileAlreadyExistsException(p.toString(), p.toString(), "Already exists as a file"); - } - } else { - SftpEventListener listener = getSftpEventListenerProxy(); - ServerSession session = getServerSession(); - listener.creating(session, p, attrs); - try { - Files.createDirectory(p); - doSetAttributes(p, attrs); - } catch (IOException | RuntimeException e) { - listener.created(session, p, attrs, e); - throw e; - } - listener.created(session, p, attrs, null); - } - } - - protected void doRemove(Buffer buffer, int id) throws IOException { - String path = buffer.getString(); - try { - /* - * If 'filename' is a symbolic link, the link is removed, - * not the file it points to. - */ - doRemove(id, path, IoUtils.getLinkOptions(false)); - } catch (IOException | RuntimeException e) { - sendStatus(BufferUtils.clear(buffer), id, e, SftpConstants.SSH_FXP_REMOVE, path); - return; - } - - sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, ""); - } - - protected void doRemove(int id, String path, LinkOption... options) throws IOException { - Path p = resolveFile(path); - if (log.isDebugEnabled()) { - log.debug("doRemove({})[id={}] SSH_FXP_REMOVE (path={}[{}])", - getServerSession(), id, path, p); - } - - Boolean status = IoUtils.checkFileExists(p, options); - if (status == null) { - throw new AccessDeniedException(p.toString(), p.toString(), "Cannot determine existence of remove candidate"); - } - if (!status) { - throw new NoSuchFileException(p.toString(), p.toString(), "Removal candidate not found"); - } else if (Files.isDirectory(p, options)) { - throw new SftpException(SftpConstants.SSH_FX_FILE_IS_A_DIRECTORY, p.toString() + " is a folder"); - } else { - doRemove(id, p); - } - } - - protected void doExtended(Buffer buffer, int id) throws IOException { - executeExtendedCommand(buffer, id, buffer.getString()); - } - - /** - * @param buffer The command {@link Buffer} - * @param id The request id - * @param extension The extension name - * @throws IOException If failed to execute the extension - */ - protected abstract void executeExtendedCommand(Buffer buffer, int id, String extension) throws IOException; - - protected void appendExtensions(Buffer buffer, String supportedVersions) { - appendVersionsExtension(buffer, supportedVersions); - appendNewlineExtension(buffer, resolveNewlineValue(getServerSession())); - appendVendorIdExtension(buffer, VersionProperties.getVersionProperties()); - appendOpenSSHExtensions(buffer); - appendAclSupportedExtension(buffer); - - Map<String, OptionalFeature> extensions = getSupportedClientExtensions(); - int numExtensions = GenericUtils.size(extensions); - List<String> extras = (numExtensions <= 0) ? Collections.emptyList() : new ArrayList<>(numExtensions); - if (numExtensions > 0) { - ServerSession session = getServerSession(); - boolean debugEnabled = log.isDebugEnabled(); - extensions.forEach((name, f) -> { - if (!f.isSupported()) { - if (debugEnabled) { - log.debug("appendExtensions({}) skip unsupported extension={}", session, name); - } - return; - } - - extras.add(name); - }); - } - appendSupportedExtension(buffer, extras); - appendSupported2Extension(buffer, extras); - } - - protected int appendAclSupportedExtension(Buffer buffer) { - ServerSession session = getServerSession(); - Collection<Integer> maskValues = resolveAclSupportedCapabilities(session); - int mask = AclSupportedParser.AclCapabilities.constructAclCapabilities(maskValues); - if (mask != 0) { - if (log.isTraceEnabled()) { - log.trace("appendAclSupportedExtension({}) capabilities={}", - session, AclSupportedParser.AclCapabilities.decodeAclCapabilities(mask)); - } - - buffer.putString(SftpConstants.EXT_ACL_SUPPORTED); - - // placeholder for length - int lenPos = buffer.wpos(); - buffer.putInt(0); - buffer.putInt(mask); - BufferUtils.updateLengthPlaceholder(buffer, lenPos); - } - - return mask; - } - - protected Collection<Integer> resolveAclSupportedCapabilities(ServerSession session) { - String override = session.getString(ACL_SUPPORTED_MASK_PROP); - if (override == null) { - return DEFAULT_ACL_SUPPORTED_MASK; - } - - // empty means not supported - if (log.isDebugEnabled()) { - log.debug("resolveAclSupportedCapabilities({}) override='{}'", session, override); - } - - if (override.length() == 0) { - return Collections.emptySet(); - } - - String[] names = GenericUtils.split(override, ','); - Set<Integer> maskValues = new HashSet<>(names.length); - for (String n : names) { - Integer v = ValidateUtils.checkNotNull( - AclSupportedParser.AclCapabilities.getAclCapabilityValue(n), "Unknown ACL capability: %s", n); - maskValues.add(v); - } - - return maskValues; - } - - protected List<OpenSSHExtension> appendOpenSSHExtensions(Buffer buffer) { - List<OpenSSHExtension> extList = resolveOpenSSHExtensions(getServerSession()); - if (GenericUtils.isEmpty(extList)) { - return extList; - } - - for (OpenSSHExtension ext : extList) { - buffer.putString(ext.getName()); - buffer.putString(ext.getVersion()); - } - - return extList; - } - - protected List<OpenSSHExtension> resolveOpenSSHExtensions(ServerSession session) { - String value = session.getString(OPENSSH_EXTENSIONS_PROP); - if (value == null) { // No override - return DEFAULT_OPEN_SSH_EXTENSIONS; - } - - if (log.isDebugEnabled()) { - log.debug("resolveOpenSSHExtensions({}) override='{}'", session, value); - } - - String[] pairs = GenericUtils.split(value, ','); - int numExts = GenericUtils.length(pairs); - if (numExts <= 0) { // User does not want to report ANY extensions - return Collections.emptyList(); - } - - List<OpenSSHExtension> extList = new ArrayList<>(numExts); - for (String nvp : pairs) { - nvp = GenericUtils.trimToEmpty(nvp); - if (GenericUtils.isEmpty(nvp)) { - continue; - } - - int pos = nvp.indexOf('='); - ValidateUtils.checkTrue((pos > 0) && (pos < (nvp.length() - 1)), "Malformed OpenSSH extension spec: %s", nvp); - String name = GenericUtils.trimToEmpty(nvp.substring(0, pos)); - String version = GenericUtils.trimToEmpty(nvp.substring(pos + 1)); - extList.add(new OpenSSHExtension(name, ValidateUtils.checkNotNullAndNotEmpty(version, "No version specified for OpenSSH extension %s", name))); - } - - return extList; - } - - protected Map<String, OptionalFeature> getSupportedClientExtensions() { - ServerSession session = getServerSession(); - String value = session.getString(CLIENT_EXTENSIONS_PROP); - if (value == null) { - return DEFAULT_SUPPORTED_CLIENT_EXTENSIONS; - } - - if (log.isDebugEnabled()) { - log.debug("getSupportedClientExtensions({}) override='{}'", session, value); - } - - if (value.length() <= 0) { // means don't report any extensions - return Collections.emptyMap(); - } - - if (value.indexOf(',') <= 0) { - return Collections.singletonMap(value, OptionalFeature.TRUE); - } - - String[] comps = GenericUtils.split(value, ','); - Map<String, OptionalFeature> result = new LinkedHashMap<>(comps.length); - for (String c : comps) { - result.put(c, OptionalFeature.TRUE); - } - - return result; - } - - /** - * Appends the "versions" extension to the buffer. <B>Note:</B> - * if overriding this method make sure you either do not append anything - * or use the correct extension name - * - * @param buffer The {@link Buffer} to append to - * @param value The recommended value - ignored if {@code null}/empty - * @see SftpConstants#EXT_VERSIONS - */ - protected void appendVersionsExtension(Buffer buffer, String value) { - if (GenericUtils.isEmpty(value)) { - return; - } - - if (log.isDebugEnabled()) { - log.debug("appendVersionsExtension({}) value={}", getServerSession(), value); - } - - buffer.putString(SftpConstants.EXT_VERSIONS); - buffer.putString(value); - } - - /** - * Appends the "newline" extension to the buffer. <B>Note:</B> - * if overriding this method make sure you either do not append anything - * or use the correct extension name - * - * @param buffer The {@link Buffer} to append to - * @param value The recommended value - ignored if {@code null}/empty - * @see SftpConstants#EXT_NEWLINE - */ - protected void appendNewlineExtension(Buffer buffer, String value) { - if (GenericUtils.isEmpty(value)) { - return; - } - - if (log.isDebugEnabled()) { - log.debug("appendNewlineExtension({}) value={}", - getServerSession(), BufferUtils.toHex(':', value.getBytes(StandardCharsets.UTF_8))); - } - - buffer.putString(SftpConstants.EXT_NEWLINE); - buffer.putString(value); - } - - protected String resolveNewlineValue(ServerSession session) { - String value = session.getString(NEWLINE_VALUE); - if (value == null) { - return IoUtils.EOL; - } else { - return value; // empty means disabled - } - } - - /** - * Appends the "vendor-id" extension to the buffer. <B>Note:</B> - * if overriding this method make sure you either do not append anything - * or use the correct extension name - * - * @param buffer The {@link Buffer} to append to - * @param versionProperties The currently available version properties - ignored - * if {@code null}/empty. The code expects the following values: - * <UL> - * <LI>{@code groupId} - as the vendor name</LI> - * <LI>{@code artifactId} - as the product name</LI> - * <LI>{@code version} - as the product version</LI> - * </UL> - * @see SftpConstants#EXT_VENDOR_ID - * @see <A HREF="http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/draft-ietf-secsh-filexfer-09.txt">DRAFT 09 - section 4.4</A> - */ - protected void appendVendorIdExtension(Buffer buffer, Map<String, ?> versionProperties) { - if (GenericUtils.isEmpty(versionProperties)) { - return; - } - - if (log.isDebugEnabled()) { - log.debug("appendVendorIdExtension({}): {}", getServerSession(), versionProperties); - } - buffer.putString(SftpConstants.EXT_VENDOR_ID); - - PropertyResolver resolver = PropertyResolverUtils.toPropertyResolver(Collections.unmodifiableMap(versionProperties)); - // placeholder for length - int lenPos = buffer.wpos(); - buffer.putInt(0); - buffer.putString(resolver.getStringProperty("groupId", getClass().getPackage().getName())); // vendor-name - buffer.putString(resolver.getStringProperty("artifactId", getClass().getSimpleName())); // product-name - buffer.putString(resolver.getStringProperty("version", FactoryManager.DEFAULT_VERSION)); // product-version - buffer.putLong(0L); // product-build-number - BufferUtils.updateLengthPlaceholder(buffer, lenPos); - } - - /** - * Appends the "supported" extension to the buffer. <B>Note:</B> - * if overriding this method make sure you either do not append anything - * or use the correct extension name - * - * @param buffer The {@link Buffer} to append to - * @param extras The extra extensions that are available and can be reported - * - may be {@code null}/empty - */ - protected void appendSupportedExtension(Buffer buffer, Collection<String> extras) { - buffer.putString(SftpConstants.EXT_SUPPORTED); - - int lenPos = buffer.wpos(); - buffer.putInt(0); // length placeholder - // supported-attribute-mask - buffer.putInt(SftpConstants.SSH_FILEXFER_ATTR_SIZE | SftpConstants.SSH_FILEXFER_ATTR_PERMISSIONS - | SftpConstants.SSH_FILEXFER_ATTR_ACCESSTIME | SftpConstants.SSH_FILEXFER_ATTR_CREATETIME - | SftpConstants.SSH_FILEXFER_ATTR_MODIFYTIME | SftpConstants.SSH_FILEXFER_ATTR_OWNERGROUP - | SftpConstants.SSH_FILEXFER_ATTR_BITS); - // TODO: supported-attribute-bits - buffer.putInt(0); - // supported-open-flags - buffer.putInt(SftpConstants.SSH_FXF_READ | SftpConstants.SSH_FXF_WRITE | SftpConstants.SSH_FXF_APPEND - | SftpConstants.SSH_FXF_CREAT | SftpConstants.SSH_FXF_TRUNC | SftpConstants.SSH_FXF_EXCL); - // TODO: supported-access-mask - buffer.putInt(0); - // max-read-size - buffer.putInt(0); - // supported extensions - buffer.putStringList(extras, false); - - BufferUtils.updateLengthPlaceholder(buffer, lenPos); - } - - /** - * Appends the "supported2" extension to the buffer. <B>Note:</B> - * if overriding this method make sure you either do not append anything - * or use the correct extension name - * - * @param buffer The {@link Buffer} to append to - * @param extras The extra extensions that are available and can be reported - * - may be {@code null}/empty - * @see SftpConstants#EXT_SUPPORTED - * @see <A HREF="https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#page-10">DRAFT 13 section 5.4</A> - */ - protected void appendSupported2Extension(Buffer buffer, Collection<String> extras) { - buffer.putString(SftpConstants.EXT_SUPPORTED2); - - int lenPos = buffer.wpos(); - buffer.putInt(0); // length placeholder - // supported-attribute-mask - buffer.putInt(SftpConstants.SSH_FILEXFER_ATTR_SIZE | SftpConstants.SSH_FILEXFER_ATTR_PERMISSIONS - | SftpConstants.SSH_FILEXFER_ATTR_ACCESSTIME | SftpConstants.SSH_FILEXFER_ATTR_CREATETIME - | SftpConstants.SSH_FILEXFER_ATTR_MODIFYTIME | SftpConstants.SSH_FILEXFER_ATTR_OWNERGROUP - | SftpConstants.SSH_FILEXFER_ATTR_BITS); - // TODO: supported-attribute-bits - buffer.putInt(0); - // supported-open-flags - buffer.putInt(SftpConstants.SSH_FXF_ACCESS_DISPOSITION | SftpConstants.SSH_FXF_APPEND_DATA); - // TODO: supported-access-mask - buffer.putInt(0); - // max-read-size - buffer.putInt(0); - // supported-open-block-vector - buffer.putShort(0); - // supported-block-vector - buffer.putShort(0); - // attrib-extension-count + attributes name - buffer.putStringList(Collections.<String>emptyList(), true); - // extension-count + supported extensions - buffer.putStringList(extras, true); - - BufferUtils.updateLengthPlaceholder(buffer, lenPos); - } - - protected void sendHandle(Buffer buffer, int id, String handle) throws IOException { - buffer.putByte((byte) SftpConstants.SSH_FXP_HANDLE); - buffer.putInt(id); - buffer.putString(handle); - send(buffer); - } - - protected void sendAttrs(Buffer buffer, int id, Map<String, ?> attributes) throws IOException { - buffer.putByte((byte) SftpConstants.SSH_FXP_ATTRS); - buffer.putInt(id); - writeAttrs(buffer, attributes); - send(buffer); - } - - protected void sendLink(Buffer buffer, int id, String link) throws IOException { - //in case we are running on Windows - String unixPath = link.replace(File.separatorChar, '/'); - - buffer.putByte((byte) SftpConstants.SSH_FXP_NAME); - buffer.putInt(id); - buffer.putInt(1); // one response - buffer.putString(unixPath); - - /* - * As per the spec (https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-6.10): - * - * The server will respond with a SSH_FXP_NAME packet containing only - * one name and a dummy attributes value. - */ - Map<String, Object> attrs = Collections.emptyMap(); - int version = getVersion(); - if (version == SftpConstants.SFTP_V3) { - buffer.putString(SftpHelper.getLongName(unixPath, attrs)); - } - - writeAttrs(buffer, attrs); - SftpHelper.indicateEndOfNamesList(buffer, getVersion(), getServerSession()); - send(buffer); - } - - protected void sendPath(Buffer buffer, int id, Path f, Map<String, ?> attrs) throws IOException { - buffer.putByte((byte) SftpConstants.SSH_FXP_NAME); - buffer.putInt(id); - buffer.putInt(1); // one reply - - String originalPath = f.toString(); - //in case we are running on Windows - String unixPath = originalPath.replace(File.separatorChar, '/'); - buffer.putString(unixPath); - - int version = getVersion(); - if (version == SftpConstants.SFTP_V3) { - buffer.putString(getLongName(f, getShortName(f), attrs)); - } - - writeAttrs(buffer, attrs); - SftpHelper.indicateEndOfNamesList(buffer, getVersion(), getServerSession()); - send(buffer); - } - - /** - * @param id Request id - * @param handle The (opaque) handle assigned to this directory - * @param dir The {@link DirectoryHandle} - * @param buffer The {@link Buffer} to write the results - * @param maxSize Max. buffer size - * @param options The {@link LinkOption}-s to use when querying the directory contents - * @return Number of written entries - * @throws IOException If failed to generate an entry - */ - protected int doReadDir( - int id, String handle, DirectoryHandle dir, Buffer buffer, int maxSize, LinkOption... options) throws IOException { - int nb = 0; - Map<String, Path> entries = new TreeMap<>(Comparator.naturalOrder()); - while ((dir.isSendDot() || dir.isSendDotDot() || dir.hasNext()) && (buffer.wpos() < maxSize)) { - if (dir.isSendDot()) { - writeDirEntry(id, dir, entries, buffer, nb, dir.getFile(), ".", options); - dir.markDotSent(); // do not send it again - } else if (dir.isSendDotDot()) { - Path dirPath = dir.getFile(); - writeDirEntry(id, dir, entries, buffer, nb, dirPath.getParent(), "..", options); - dir.markDotDotSent(); // do not send it again - } else { - Path f = dir.next(); - writeDirEntry(id, dir, entries, buffer, nb, f, getShortName(f), options); - } - - nb++; - } - - SftpEventListener listener = getSftpEventListenerProxy(); - listener.read(getServerSession(), handle, dir, entries); - return nb; - } - - /** - * @param id Request id - * @param dir The {@link DirectoryHandle} - * @param entries An in / out {@link Map} for updating the written entry - - * key = short name, value = entry {@link Path} - * @param buffer The {@link Buffer} to write the results - * @param index Zero-based index of the entry to be written - * @param f The entry {@link Path} - * @param shortName The entry short name - * @param options The {@link LinkOption}s to use for querying the entry-s attributes - * @throws IOException If failed to generate the entry data - */ - protected void writeDirEntry( - int id, DirectoryHandle dir, Map<String, Path> entries, Buffer buffer, int index, Path f, String shortName, LinkOption... options) - throws IOException { - Map<String, ?> attrs = resolveFileAttributes(f, SftpConstants.SSH_FILEXFER_ATTR_ALL, options); - entries.put(shortName, f); - - buffer.putString(shortName); - int version = getVersion(); - if (version == SftpConstants.SFTP_V3) { - String longName = getLongName(f, shortName, options); - buffer.putString(longName); - if (log.isTraceEnabled()) { - log.trace("writeDirEntry(" + getServerSession() + ") id=" + id + ")[" + index + "] - " - + shortName + " [" + longName + "]: " + attrs); - } - } else { - if (log.isTraceEnabled()) { - log.trace("writeDirEntry(" + getServerSession() + "(id=" + id + ")[" + index + "] - " - + shortName + ": " + attrs); - } - } - - writeAttrs(buffer, attrs); - } - - protected String getLongName(Path f, String shortName, LinkOption... options) throws IOException { - return getLongName(f, shortName, true, options); - } - - protected String getLongName(Path f, String shortName, boolean sendAttrs, LinkOption... options) throws IOException { - Map<String, Object> attributes; - if (sendAttrs) { - attributes = getAttributes(f, options); - } else { - attributes = Collections.emptyMap(); - } - return getLongName(f, shortName, attributes); - } - - protected String getLongName(Path f, String shortName, Map<String, ?> attributes) throws IOException { - return SftpHelper.getLongName(shortName, attributes); - } - - protected String getShortName(Path f) throws IOException { - Path nrm = normalize(f); - int count = nrm.getNameCount(); - /* - * According to the javadoc: - * - * The number of elements in the path, or 0 if this path only - * represents a root component - */ - if (OsUtils.isUNIX()) { - Path name = f.getFileName(); - if (name == null) { - Path p = resolveFile("."); - name = p.getFileName(); - } - - if (name == null) { - if (count > 0) { - name = nrm.getFileName(); - } - } - - if (name != null) { - return name.toString(); - } else { - return nrm.toString(); - } - } else { // need special handling for Windows root drives - if (count > 0) { - Path name = nrm.getFileName(); - return name.toString(); - } else { - return nrm.toString().replace(File.separatorChar, '/'); - } - } - } - - protected NavigableMap<String, Object> resolveFileAttributes(Path file, int flags, LinkOption... options) throws IOException { - Boolean status = IoUtils.checkFileExists(file, options); - if (status == null) { - return handleUnknownStatusFileAttributes(file, flags, options); - } else if (!status) { - throw new NoSuchFileException(file.toString(), file.toString(), "Attributes N/A for target"); - } else { - return getAttributes(file, flags, options); - } - } - - protected void writeAttrs(Buffer buffer, Map<String, ?> attributes) throws IOException { - SftpHelper.writeAttrs(buffer, getVersion(), attributes); - } - - protected NavigableMap<String, Object> getAttributes(Path file, LinkOption... options) throws IOException { - return getAttributes(file, SftpConstants.SSH_FILEXFER_ATTR_ALL, options); - } - - protected NavigableMap<String, Object> handleUnknownStatusFileAttributes(Path file, int flags, LinkOption... options) throws IOException { - UnsupportedAttributePolicy unsupportedAttributePolicy = getUnsupportedAttributePolicy(); - switch (unsupportedAttributePolicy) { - case Ignore: - break; - case ThrowException: - throw new AccessDeniedException(file.toString(), file.toString(), "Cannot determine existence for attributes of target"); - case Warn: - log.warn("handleUnknownStatusFileAttributes(" + getServerSession() + ")[" + file + "] cannot determine existence"); - break; - default: - log.warn("handleUnknownStatusFileAttributes(" + getServerSession() + ")[" + file + "] unknown policy: " + unsupportedAttributePolicy); - } - - return getAttributes(file, flags, options); - } - - /** - * @param file The {@link Path} location for the required attributes - * @param flags A mask of the original required attributes - ignored by the - * default implementation - * @param options The {@link LinkOption}s to use in order to access the file - * if necessary - * @return A {@link Map} of the retrieved attributes - * @throws IOException If failed to access the file - * @see #resolveMissingFileAttributes(Path, int, Map, LinkOption...) - */ - protected NavigableMap<String, Object> getAttributes(Path file, int flags, LinkOption... options) throws IOException { - FileSystem fs = file.getFileSystem(); - Collection<String> supportedViews = fs.supportedFileAttributeViews(); - NavigableMap<String, Object> attrs = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - Collection<String> views; - - if (GenericUtils.isEmpty(supportedViews)) { - views = Collections.emptyList(); - } else if (supportedViews.contains("unix")) { -
<TRUNCATED>